From 4599f26ae9cda03394bd6dc5902b946266c65f3a Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Wed, 18 Feb 2026 05:31:53 -0600 Subject: [PATCH] refactor(Core/Spells): QAston proc system (#24233) Co-authored-by: blinkysc Co-authored-by: QAston Co-authored-by: joschiwald Co-authored-by: ariel- Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Co-authored-by: blinkysc Co-authored-by: Tereneckla Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com> --- data/sql/updates/db_world/2026_01_24_00.sql | 5 - .../rev_1766547838660651694.sql | 1932 ++++++++ .../rev_1769292856469402686.sql | 105 + .../rev_1769396722032747978.sql | 18 + .../rev_1769728867586741519.sql | 31 + .../rev_1769815350392767493.sql | 4 + .../rev_1769974041727457907.sql | 2 + .../rev_1769983684458633094.sql | 8 + src/server/game/Entities/Player/Player.cpp | 105 +- src/server/game/Entities/Player/Player.h | 22 +- src/server/game/Entities/Unit/Unit.cpp | 4338 +---------------- src/server/game/Entities/Unit/Unit.h | 32 +- src/server/game/Handlers/CharacterHandler.cpp | 2 +- .../game/Spells/Auras/SpellAuraEffects.cpp | 165 +- .../game/Spells/Auras/SpellAuraEffects.h | 1 + src/server/game/Spells/Auras/SpellAuras.cpp | 305 +- src/server/game/Spells/Auras/SpellAuras.h | 19 +- src/server/game/Spells/Spell.cpp | 151 +- src/server/game/Spells/Spell.h | 1 + src/server/game/Spells/SpellEffects.cpp | 33 - src/server/game/Spells/SpellInfo.cpp | 1 - .../game/Spells/SpellInfoCorrections.cpp | 16 - src/server/game/Spells/SpellMgr.cpp | 472 +- src/server/game/Spells/SpellMgr.h | 46 +- src/server/game/Spells/SpellScript.cpp | 20 +- src/server/game/Spells/SpellScript.h | 20 +- src/server/game/World/World.cpp | 3 - src/server/scripts/Commands/cs_reload.cpp | 10 - .../BattleForMountHyjal/boss_anetheron.cpp | 30 + .../boss_deathbringer_saurfang.cpp | 23 + .../boss_professor_putricide.cpp | 26 + .../Northrend/Ulduar/Ulduar/boss_xt002.cpp | 34 + .../UtgardeKeep/UtgardeKeep/utgarde_keep.cpp | 39 + .../scripts/Outland/boss_doomlord_kazzak.cpp | 34 +- src/server/scripts/Pet/pet_hunter.cpp | 139 + src/server/scripts/Spells/spell_dk.cpp | 681 ++- src/server/scripts/Spells/spell_druid.cpp | 885 +++- src/server/scripts/Spells/spell_generic.cpp | 395 ++ src/server/scripts/Spells/spell_hunter.cpp | 463 +- src/server/scripts/Spells/spell_item.cpp | 1955 ++++++++ src/server/scripts/Spells/spell_mage.cpp | 714 ++- src/server/scripts/Spells/spell_paladin.cpp | 1171 ++++- src/server/scripts/Spells/spell_priest.cpp | 470 ++ src/server/scripts/Spells/spell_rogue.cpp | 227 + src/server/scripts/Spells/spell_shaman.cpp | 1094 ++++- src/server/scripts/Spells/spell_warlock.cpp | 504 ++ src/server/scripts/Spells/spell_warrior.cpp | 311 +- src/server/shared/SharedDefines.h | 4 +- src/server/shared/enuminfo_SharedDefines.cpp | 4 +- src/test/CMakeLists.txt | 2 +- src/test/mocks/AuraScriptTestFramework.h | 484 ++ src/test/mocks/AuraStub.h | 367 ++ src/test/mocks/DamageHealInfoStub.h | 259 + src/test/mocks/ProcChanceTestHelper.h | 565 +++ src/test/mocks/ProcEventInfoHelper.h | 235 + src/test/mocks/SpellInfoTestHelper.h | 215 + src/test/mocks/UnitStub.h | 244 + .../game/Spells/SpellProcAttributeTest.cpp | 445 ++ .../game/Spells/SpellProcChanceTest.cpp | 317 ++ .../game/Spells/SpellProcChargeTest.cpp | 409 ++ .../game/Spells/SpellProcConditionsTest.cpp | 386 ++ .../game/Spells/SpellProcCooldownTest.cpp | 219 + .../Spells/SpellProcDBCValidationTest.cpp | 369 ++ .../game/Spells/SpellProcDataDrivenTest.cpp | 756 +++ .../Spells/SpellProcDisableEffectsTest.cpp | 275 ++ .../game/Spells/SpellProcEquipmentTest.cpp | 404 ++ .../game/Spells/SpellProcFullCoverageTest.cpp | 458 ++ .../game/Spells/SpellProcIntegrationTest.cpp | 565 +++ .../game/Spells/SpellProcPPMModifierTest.cpp | 399 ++ .../server/game/Spells/SpellProcPPMTest.cpp | 377 ++ .../game/Spells/SpellProcPipelineTest.cpp | 462 ++ .../Spells/SpellProcSpellTypeMaskTest.cpp | 226 + src/test/server/game/Spells/SpellProcTest.cpp | 903 ++++ .../server/game/Spells/SpellProcTestData.h | 1020 ++++ .../Spells/SpellProcTriggeredFilterTest.cpp | 391 ++ .../Spells/SpellScriptMissileBarrageTest.cpp | 274 ++ 76 files changed, 22915 insertions(+), 5181 deletions(-) delete mode 100644 data/sql/updates/db_world/2026_01_24_00.sql create mode 100644 data/sql/updates/pending_db_world/rev_1766547838660651694.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769292856469402686.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769396722032747978.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769728867586741519.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769815350392767493.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769974041727457907.sql create mode 100644 data/sql/updates/pending_db_world/rev_1769983684458633094.sql create mode 100644 src/test/mocks/AuraScriptTestFramework.h create mode 100644 src/test/mocks/AuraStub.h create mode 100644 src/test/mocks/DamageHealInfoStub.h create mode 100644 src/test/mocks/ProcChanceTestHelper.h create mode 100644 src/test/mocks/ProcEventInfoHelper.h create mode 100644 src/test/mocks/SpellInfoTestHelper.h create mode 100644 src/test/mocks/UnitStub.h create mode 100644 src/test/server/game/Spells/SpellProcAttributeTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcChanceTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcChargeTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcConditionsTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcCooldownTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcDBCValidationTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcDataDrivenTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcDisableEffectsTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcEquipmentTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcFullCoverageTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcIntegrationTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcPPMModifierTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcPPMTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcPipelineTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcSpellTypeMaskTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcTest.cpp create mode 100644 src/test/server/game/Spells/SpellProcTestData.h create mode 100644 src/test/server/game/Spells/SpellProcTriggeredFilterTest.cpp create mode 100644 src/test/server/game/Spells/SpellScriptMissileBarrageTest.cpp diff --git a/data/sql/updates/db_world/2026_01_24_00.sql b/data/sql/updates/db_world/2026_01_24_00.sql deleted file mode 100644 index 067005d15..000000000 --- a/data/sql/updates/db_world/2026_01_24_00.sql +++ /dev/null @@ -1,5 +0,0 @@ --- DB update 2026_01_23_03 -> 2026_01_24_00 --- -DELETE FROM `spell_proc_event` WHERE `entry` = -49182; -INSERT INTO `spell_proc_event` (`entry`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `procFlags`, `procEx`, `procPhase`, `ppmRate`, `CustomChance`, `Cooldown`) VALUES -(-49182, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0); diff --git a/data/sql/updates/pending_db_world/rev_1766547838660651694.sql b/data/sql/updates/pending_db_world/rev_1766547838660651694.sql new file mode 100644 index 000000000..0fe6f5bbe --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1766547838660651694.sql @@ -0,0 +1,1932 @@ +-- QAston Proc System - Base spell_proc entries +-- Port from TrinityCore QAston proc system commits + +-- Add DisableEffectsMask column to spell_proc table if it doesn't exist +SET @column_exists = (SELECT 1 FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'spell_proc' AND `COLUMN_NAME` = 'DisableEffectsMask' LIMIT 1); +SET @sql = IF(@column_exists IS NULL, 'ALTER TABLE `spell_proc` ADD COLUMN `DisableEffectsMask` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `AttributesMask`', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Charge drop on spell cast +DELETE FROM `spell_proc` WHERE `SpellId` IN (17941, 18820, 22008, 28200, 32216, 34477, 34936, 44401, 48108, 51124, 54741, 57761, 64823); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(17941, 0, 5, 0x00000001, 0x00000000, 0x00000000, 65536, 0x1, 0x1, 0, 0x0, 0, 0, 0, 1), -- Shadow Trance +(18820, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x7, 0x1, 0, 0x0, 0, 0, 0, 1), -- Insight +(22008, 0, 3, 0x61400035, 0x00000000, 0x00000000, 69632, 0x5, 0x1, 0, 0x0, 0, 0, 0, 1), -- Netherwind Focus +(28200, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x1, 0, 0x0, 0, 0, 0, 6), -- Ascendance +(32216, 0, 4, 0x00000000, 0x00000100, 0x00000000, 16, 0x1, 0x4, 0, 0x0, 0, 0, 0, 1), -- Victorious (drop charge on Victory rush cast) +(34477, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x5, 0x2, 0, 0x0, 0, 0, 0, 1), -- Misdirection +(34936, 0, 5, 0x00000001, 0x00000040, 0x00000000, 65536, 0x1, 0x1, 0, 0x8, 0, 0, 0, 1), -- Backlash +(44401, 0, 3, 0x00000800, 0x00000000, 0x00000000, 4096, 0x5, 0x1, 0, 0x8, 0, 0, 0, 1), -- Missile Barrage +(48108, 0, 3, 0x00400000, 0x00000000, 0x00000000, 65536, 0x1, 0x1, 0, 0x8, 0, 0, 0, 1), -- Hot Streak +(51124, 0, 15, 0x00000002, 0x00000006, 0x00000000, 65552, 0x1, 0x2, 0, 0x8, 0, 0, 0, 1), -- Killing Machine +(54741, 0, 3, 0x00000004, 0x00000000, 0x00000000, 65536, 0x5, 0x1, 0, 0x0, 0, 0, 0, 1), -- Firestarter +(57761, 0, 3, 0x00000001, 0x00001000, 0x00000000, 65536, 0x1, 0x1, 0, 0x8, 0, 0, 0, 1), -- Fireball! +(64823, 0, 7, 0x00000004, 0x00000000, 0x00000000, 65536, 0x1, 0x1, 0, 0x0, 0, 0, 0, 1); -- Elune's Wrath +-- Port procs from spell_proc_event table +DELETE FROM `spell_proc` WHERE `SpellId` IN (-974, -1463, -10400, -11119, -11185, -12834, -13983, -14156, -15337, -16180, -18094, -18213, -20234, -20335, -27243, -29441, -29723, -29834, -30293, -30675, -31244, -31571, -31656, -31785, -31871, -31876, -34497, -34914, -44404, -44445, -44546, -46913, -46951, -47569, -48539, -48979, -49015, -49018, -49182, -49188, -49208, -49217, -49467, -51459, -51474, -51525, -51556, -51625, -51627, -51664, -53178, -53228, -53290, -53380, -53501, -53569, -53695, -54639, -54747, -59088, -61680, -62764, -63156, -63373, -64127, -65661, 1719, 11129, 12536, 15286, 16246, 16864, 16870, 17619, 20185, 20186, 22007, 24658, 24932, 26169, 26467, 28716, 28719, 28744, 28789, 28809, 28823, 28845, 28847, 28849, 29601, 32863, 36123, 38252, 39367, 44141, 70388, 30823, 31801, 32409, 33757, 37288, 37295, 37381, 37377, 39437, 37168, 37594, 38164, 39372, 40438, 40442, 40463, 40470, 40971, 42770, 45057, 46916, 47383, 71162, 71165, 49005, 49028, 49194, 49222, 49796, 51209, 51528, 51529, 51530, 51531, 51532, 51698, 51700, 51701, 52420, 52437, 53601, 53646, 53736, 54274, 54276, 54277, 54748, 54754, 54815, 54821, 54832, 54845, 54909, 54937, 54939, 55198, 55440, 55677, 56218, 56372, 56374, 56375, 56800, 57870, 58375, 58642, 58677, 58877, 59906, 59915, 37447, 61062, 61257, 62259, 62600, 62606, 63279, 63280, 63320, 64890, 64928, 65032, 67228, 69755, 69739, 69762, 70723, 70664, 70770, 70805, 70808, 70817, 70844, 70672, 72455, 72832, 72833, 71756, 72782, 72783, 72784, 71406, 71545, 71880, 71892, 71519, 71562, 71564, 71634, 71640, 71761, 71770, 72176, 75475, 75481); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(-974, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 3000, 0), -- Earth Shield +(-1463, 0, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1024, 0x0, 0, 0, 0, 0), -- Mana Shield +(-10400, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Flametongue Weapon (Passive) +(-11119, 4, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Ignite +(-11185, 0, 3, 0x00000080, 0x00000000, 0x00000000, 65536, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0), -- Improved Blizzard +(-12834, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Deep Wounds Aura +(-13983, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 24, 0x0, 0, 0, 1000, 0), -- Setup +(-14156, 0, 8, 0x003E0000, 0x00000009, 0x00000000, 0, 0x0, 0x4, 0, 0x0, 0, 0, 0, 0), -- Ruthlessness +(-15337, 0, 6, 0x00802000, 0x00000002, 0x00000000, 0, 0x1, 0x2, 2, 0x2, 0, 0, 0, 0), -- Improved Spirit Tap +(-16180, 0, 11, 0x000001C0, 0x00000000, 0x00000010, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Improved Water Shield +(-18094, 0, 5, 0x0000000A, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Nightfall +(-18213, 32, 5, 0x00004000, 0x00000000, 0x00000000, 2, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Improved Drain Soul +(-20234, 0, 10, 0x00008000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Lay on Hands +(-20335, 0, 10, 0x00800000, 0x00000000, 0x00000000, 16, 0x5, 0x2, 0, 0x0, 0, 100, 0, 0), -- Heart of the Crusader +(-27243, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of corruption (Warlock) +(-29441, 0, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x7, 0x0, 8, 0x0, 0, 0, 1000, 0), -- Magic Absorption +(-29723, 0, 4, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sudden Death +(-29834, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x5, 0x0, 0, 0x0, 0, 0, 0, 0), -- Second Wind (Warrior talent) +(-30293, 0, 5, 0x00000181, 0x008200C0, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Soul Leech +(-30675, 0, 11, 0x00000003, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Lightning Overload +(-31244, 0, 8, 0x003A0000, 0x00000009, 0x00000000, 0, 0x5, 0x2, 11196, 0x0, 0, 0, 0, 0), -- Quick Recovery +(-31571, 0, 3, 0x00000000, 0x00000022, 0x00000008, 16384, 0x7, 0x4, 0, 0x0, 0, 0, 0, 0), -- Arcane Potency +(-31656, 4, 3, 0x08000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Empowered Fire +(-31785, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Spiritual Attunement +(-31871, 0, 10, 0x00000010, 0x00000000, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Divine Purpose +(-31876, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Judgements of the Wise +(-34497, 0, 9, 0x00060800, 0x00800001, 0x00000201, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Thrill of the Hunt +(-34914, 0, 6, 0x00002000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Vampiric Touch +(-44404, 0, 3, 0x20000021, 0x00009000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Missile Barrage +(-44445, 0, 3, 0x00000013, 0x00011000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Hot Streak +(-44546, 0, 3, 0x000002E0, 0x00001000, 0x00000000, 69632, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Brain Freeze +(-46913, 0, 4, 0x00000040, 0x00000404, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Bloodsurge +(-46951, 0, 4, 0x00000400, 0x00000040, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sword and Board +(-47569, 0, 6, 0x00004000, 0x00000000, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Shadowform +(-48539, 0, 7, 0x00000010, 0x04000000, 0x00000000, 262144, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Revitalize +(-48979, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 0, 0), -- Butchery +(-49015, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 0, 0), -- Vendetta +(-49018, 0, 15, 0x01400000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sudden Doom +(-49182, 0, 15, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blade Barrier +(-49188, 0, 15, 0x00000000, 0x00020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Rime +(-49208, 0, 15, 0x00400000, 0x00010000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Reaping +(-49217, 0, 15, 0x00000000, 0x00000000, 0x00000002, 0, 0x0, 0x0, 0, 0x0, 0, 0, 1000, 0), -- Wandering Plague +(-49467, 0, 15, 0x00000010, 0x00020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Death Rune Mastery +(-51459, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Necrosis +(-51474, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Astral Shift +(-51525, 0, 11, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0 ,0x0, 0, 0, 0, 0), -- Static Shock +(-51556, 0, 11, 0x000000C0, 0x00000000, 0x00000010, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Ancestral Awakening +(-51625, 0, 8, 0x1000A000, 0x00000000, 0x00000000, 0, 0x5, 0x2, 0, 0x0, 0, 0, 0, 0), -- Deadly Brew +(-51627, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 112, 0x0, 0, 0, 0, 0), -- Turn the Tables +(-51664, 0, 8, 0x00020000, 0x00000008, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Cut to the Chase +(-53178, 0, 9, 0x00000000, 0x10000000, 0x00000000, 65536, 0x4, 0x2, 0, 0x0, 0, 100, 0, 0), -- Guard Dog +(-53228, 0, 9, 0x00000020, 0x01000000, 0x00000000, 0, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Rapid Recuperation +(-53290, 0, 9, 0x00000800, 0x00000001, 0x00000200, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Hunting Party +(-53380, 0, 10, 0x00800000, 0x00028000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Righteous Vengeance +(-53501, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Sheath of Light +(-53569, 0, 10, 0x40200000, 0x00010000, 0x00000000, 0, 0x3, 0x2, 0, 0x0, 0, 0, 0, 0), -- Infusion of Light +(-53695, 0, 10, 0x00800000, 0x00000000, 0x00000008, 16, 0x5, 0x2, 0, 0x2, 0, 0, 0, 0), -- Judgements of the Just +(-54639, 0, 15, 0x00400000, 0x00010000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blood of the North +(-54747, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Burning Determination +(-59088, 0, 4, 0x00000000, 0x00000002, 0x00000000, 1024, 0x4, 0x4, 0, 0x0, 0, 0, 0, 0), -- Improved Spell Reflection +(-61680, 0, 9, 0x00000000, 0x10000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Culling the Herd +(-62764, 0, 9, 0x00000000, 0x10000000, 0x00000000, 65536, 0x4, 0x2, 0, 0x0, 0, 100, 0, 0), -- Silverback +(-63156, 0, 5, 0x00000001, 0x000000C0, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Decimation +(-63373, 0, 11, 0x80000000, 0x00000000, 0x00000000, 65536, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Frozen Power +(-64127, 0, 6, 0x00000001, 0x00000001, 0x00000000, 0, 0x6, 0x2, 0, 0x0, 0, 0, 0, 0), -- Body and Soul +(-65661, 0, 15, 0x00400011, 0x20020004, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 100, 0, 0), -- Threat of Thassarian + +(1719, 0, 4, 0x2E600444, 0x00404745, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Recklessness +(11129, 4, 3, 0x08C00017, 0x00031048, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Combustion +(12536, 0, 3, 0x20C21AF7, 0x00029040, 0x00000000, 0, 0x0, 0x1, 0, 0x4, 0, 0, 0, 0), -- Clearcasting (Mage) +(15286, 32, 6, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Vampiric Embrace +(16246, 0, 11, 0x981001C3, 0x00001400, 0x00000010, 0, 0x0, 0x1, 0, 0x4, 0, 0, 0, 0), -- Clearcasting (Shaman) +(16864, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3.5, 0, 0, 0), -- Omen of Clarity +(16870, 0, 7, 0x00E3BBFF, 0x079007D3, 0x00040400, 0, 0x0, 0x1, 0, 0x4, 0, 0, 0, 0), -- Clearcasting (Druid) +(17619, 0, 13, 0x00000000, 0x00000000, 0x00000000, 32768, 0x7, 0x0, 0, 0x0, 0, 0, 0, 0), -- Alchemist's Stone +(20185, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 15, 0, 0, 0), -- Judgement of Light +(20186, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 15, 0, 0, 0), -- Judgement of Wisdom +(22007, 0, 3, 0x00200021, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Netherwind Focus +(24658, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87376, 0x7, 0x2, 0, 0x0, 0, 0, 0, 0), -- Unstable Power +(24932, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Leader of the Pack +(26169, 0, 6, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Oracle Healing Bonus +(26467, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Persistent Shield +(28716, 0, 7, 0x00000010, 0x00000000, 0x00000000, 262144, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Rejuvenation - Dreamwalker Raiment 2pc +(28719, 0, 7, 0x00000020, 0x00000000, 0x00000000, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Healing Touch - Dreamwalker Raiment 8 pc +(28744, 0, 7, 0x00000040, 0x00000000, 0x00000000, 278528, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Regrowth - Dreamwalker Raiment 6pc +(28789, 0, 10, 0xC0000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Holy Power +(28809, 0, 6, 0x00001000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Greater Heal - Vestments of Faith 4pc +(28823, 0, 11, 0x000000C0, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Totemic Power +(28845, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Cheat Death +(28847, 0, 7, 0x00000020, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Healing Touch Refund +(28849, 0, 11, 0x00000080, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Lesser Heealing Wave +(29601, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x4, 0, 0, 0, 0), -- Enlightenment + +(32863, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) +(36123, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) +(38252, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) +(39367, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) +(44141, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) +(70388, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Seed of Corruption (Monster) + +(30823, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 18, 0, 0, 0), -- Shamanistic Rage (AC #17499) +(31801, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0), -- Seal of Vengeance +(32409, 0, 0, 0x00000000, 0x00002000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Shadow Word: Death - do not require honor target +(33757, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 3000, 0), -- Windfury Weapon (Passive) +(37288, 0, 7, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Mana Restore - Malorne Raiment 2pc +(37295, 0, 7, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Mana Restore - Malorne Regalia 2pc +(37381, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Pet Healing + +(37377, 32, 5, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Shadowflame +(39437, 4, 5, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Shadowflame Hellfire and RoF + +(37168, 0, 8, 0x003E0000, 0x00000009, 0x00000000, 0, 0x0, 0x4, 0, 0x0, 0, 0, 0, 0), -- Finisher Combo +(37594, 0, 6, 0x00001000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Greater Heal Refund +(38164, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Unyielding Knights +(39372, 48, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Frozen Shadoweave +(40438, 0, 6, 0x00008040, 0x00000000, 0x00000000, 0, 0x3, 0x0, 0, 0x0, 0, 0, 0, 0), -- Priest Tier 6 Trinket +(40442, 0, 7, 0x00000014, 0x00000440, 0x00000000, 0, 0x7, 0x1, 0, 0x0, 0, 0, 0, 0), -- Druid Tier 6 Trinket +(40463, 0, 11, 0x00000081, 0x00000010, 0x00000000, 0, 0x3, 0x2, 0, 0x0, 0, 0, 0, 0), -- Shaman Tier 6 Trinket +(40470, 0, 10, 0xC0800000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 0, 0x0, 0, 0, 0, 0), -- Paladin Tier 6 Trinket +(40971, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Bonus Healing (Crystal Spire of Karabor) +(42770, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x5, 0x0, 0, 0x0, 0, 0, 0, 0), -- Second Wind (NPC aura) +(45057, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Evasive Maneuvers +(46916, 0, 4, 0x00200000, 0x00000000, 0x00000000, 0, 0x0, 0x4, 0, 0x0, 0, 0, 0, 0), -- Slam! (Bloodsurge proc) + +(47383, 0, 5, 0x00000000, 0x000000C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Molten Core +(71162, 0, 5, 0x00000000, 0x000000C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Molten Core +(71165, 0, 5, 0x00000000, 0x000000C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Molten Core + +(49005, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Mark of Blood +(49028, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x5, 0x2, 0, 0x0, 0, 0, 0, 0), -- Dancing Rune Weapon +(49194, 0, 15, 0x00000000, 0x00000000, 0x00000001, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Unholy Blight +(49222, 0, 0, 0x00000000, 0x00000000, 0x00000000, 139944, 0x0, 0x0, 0, 0x0, 0, 0, 2000, 0), -- Bone Shield +(49796, 0, 15, 0x00000002, 0x00020006, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Deathchill +(51209, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Hungering Cold + +(51528, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 4, 0, 0, 0), -- Maelstrom Weapon (Rank 1) +(51529, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 8, 0, 0, 0), -- Maelstrom Weapon (Rank 2) +(51530, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 12, 0, 0, 0), -- Maelstrom Weapon (Rank 3) +(51531, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 16, 0, 0, 0), -- Maelstrom Weapon (Rank 4) +(51532, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 20, 0, 0, 0), -- Maelstrom Weapon (Rank 5) + +(51698, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 2, 0x0, 0, 33, 0, 0), -- Honor Among Thieves (Rank 1) +(51700, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 2, 0x0, 0, 66, 0, 0), -- Honor Among Thieves (Rank 2) +(51701, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 2, 0x0, 0, 100, 0, 0), -- Honor Among Thieves (Rank 3) + +(52420, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Deflection +(52437, 1, 4, 0x20000000, 0x00000000, 0x00000000, 16, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sudden Death proc +(53601, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1048576, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Sacred Shield +(53646, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Demonic Pact +(53736, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0), -- Seal of Corruption + +(54274, 0, 5, 0x00000165, 0x000310C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Backdraft +(54276, 0, 5, 0x00000165, 0x000310C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Backdraft +(54277, 0, 5, 0x00000165, 0x000310C0, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0), -- Backdraft + +(54748, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 259, 0x0, 0, 0, 0, 0), -- Burning Determination proc +(54754, 0, 7, 0x00000010, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Glyph of Rejuvenation +(54815, 0, 7, 0x00008000, 0x00000000, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Shred +(54821, 0, 7, 0x00001000, 0x00000000, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Rake +(54832, 0, 7, 0x00000000, 0x00001000, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Innervate +(54845, 0, 7, 0x00000004, 0x00000000, 0x00000000, 65536, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Starfire +(54909, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Demonic Pact +(54937, 0, 10, 0x80000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Holy Light +(54939, 0, 10, 0x00008000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Divinity +(55198, 0, 11, 0x000001C0, 0x00000000, 0x00000000, 16384, 0x2, 0x2, 2, 0x0, 0, 0, 0, 3), -- Tidal Force +(55440, 0, 11, 0x00000040, 0x00000000, 0x00000000, 0, 0x2, 0x1, 0, 0x0, 0, 0, 0, 0), -- Glyph of Healing Wave +(55677, 0, 6, 0x00000000, 0x00000001, 0x00000000, 0, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Dispel Magic +(56218, 0, 5, 0x00000002, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Glyph of Corruption +(56372, 0, 3, 0x00000000, 0x00000080, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Ice Block +(56374, 0, 3, 0x00000000, 0x00004000, 0x00000008, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Icy Veins +(56375, 0, 3, 0x01000000, 0x00000000, 0x00000000, 65536, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Polymorph +(56800, 0, 8, 0x00000004, 0x00000000, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Backstab +(57870, 0, 9, 0x00800000, 0x00000000, 0x00000000, 262144, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Glyph of Mend Pet +(58375, 0, 4, 0x00000000, 0x00000200, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Blocking +(58642, 0, 15, 0x00000000, 0x08000000, 0x00000000, 16, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Scourge Strike +(58677, 0, 15, 0x00002000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x2, 0, 0, 0, 0), -- Glyph of Death's Embrace +(58877, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Spirit Hunt +(59906, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 0, 0), -- Swift Hand of Justice +(59915, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Discerning Eye of the Beast + +(37447, 0, 3, 0x00000000, 0x00000100, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Mana Gems +(61062, 0, 3, 0x00000000, 0x00000100, 0x00000000, 16384, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Mana Gems + +(61257, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x5, 0x0, 0, 0x0, 0, 0, 0, 0), -- Runic Power Back on Snare/Root +(62259, 0, 15, 0x02000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 0, 0), -- Glyph of Death Grip +(62600, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Savage Defense (Passive) +(62606, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 1027, 0x0, 0, 0, 0, 0), -- Savage Defense +(63279, 0, 11, 0x00000000, 0x00000400, 0x00000000, 0, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Earth Shield +(63280, 0, 11, 0x20000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Totem of Wrath +(63320, 0, 5, 0x80040000, 0x00000000, 0x00008000, 1024, 0x7, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Life Tap +(64890, 0, 10, 0x00000000, 0x00010000, 0x00000000, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Item - Paladin T8 Holy 2P Bonus +(64928, 0, 11, 0x00000001, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Item - Shaman T8 Elemental 4P Bonus +(65032, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- 321-Boombot Aura - do not require experience target +(67228, 0, 11, 0x00000000, 0x00001000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T9 Elemental 4P Bonus (Lava Burst) +(69755, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x7, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Purified Shard of the Scale +(69739, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x7, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Shiny Shard of the Scale +(69762, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Unchained Magic +(70723, 0, 7, 0x00000005, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Item - Druid T10 Balance 4P Bonus +(70664, 0, 7, 0x00000010, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Druid T10 Restoration 4P Bonus (Rejuvenation) +(70770, 0, 6, 0x00000800, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Priest T10 Healer 2P Bonus +(70805, 0, 8, 0x00000000, 0x00020000, 0x00000000, 0, 0x4, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Rogue T10 2P Bonus +(70808, 0, 11, 0x00000100, 0x00000000, 0x00000000, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Restoration 4P Bonus +(70817, 0, 11, 0x00000000, 0x00001000, 0x00000000, 65536, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Elemental 4P Bonus +(70844, 0, 4, 0x00000100, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Warrior T10 Protection 4P Bonus + +(70672, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Gaseous Bloat +(72455, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Gaseous Bloat +(72832, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Gaseous Bloat +(72833, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Gaseous Bloat + +(71756, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Ball of Flames Proc +(72782, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Ball of Flames Proc +(72783, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Ball of Flames Proc +(72784, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Ball of Flames Proc + +(71406, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 50, 0, 0), -- Anger Capacitor +(71545, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 50, 0, 0), -- Anger Capacitor + +(71880, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 1, 0, 0, 0), -- Item - Icecrown 25 Normal Dagger Proc +(71892, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 1, 0, 0, 0), -- Item - Icecrown 25 Heroic Dagger Proc + +(71519, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 105000, 0), -- Deathbringer's Will +(71562, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 105000, 0), -- Deathbringer's Will (Heroic) +(71564, 126, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 2, 0x0, 0, 0, 0, 5), -- Deadly Precision +(71634, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Item - Icecrown 25 Normal Tank Trinket 1 +(71640, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Item - Icecrown 25 Heroic Tank Trinket 1 +(71761, 3, 0, 0x00000000, 0x00100000, 0x00000000, 0, 0x5, 0x2, 256, 0x0, 0, 0, 0, 0), -- Deep Freeze Immunity State +(71770, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Ooze Spell Tank Protection +(72176, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blood Beast's Blood Link +(75475, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0), -- Item - Chamber of Aspects 25 Tank Trinket +(75481, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0); -- Item - Chamber of Aspects 25 Heroic Tank Trinket + +-- Add spellscripts to spells previously on giant switches in Unit.cpp +DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_rog_t10_2p_bonus'; +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_sha_flametongue_weapon','spell_mage_imp_blizzard','spell_warr_deep_wounds_aura','spell_rog_setup','spell_pri_improved_spirit_tap','spell_sha_imp_water_shield','spell_warl_improved_drain_soul','spell_pal_improved_lay_of_hands','spell_pal_heart_of_the_crusader','spell_warl_seed_of_corruption_dummy','spell_mage_magic_absorption','spell_warr_extra_proc','spell_warr_second_wind','spell_warl_soul_leech','spell_sha_lightning_overload','spell_rog_quick_recovery','spell_mage_arcane_potency','spell_mage_empowered_fire','spell_pal_spiritual_attunement','spell_pal_divine_purpose','spell_pal_judgements_of_the_wise','spell_hun_thrill_of_the_hunt','spell_mage_missile_barrage','spell_mage_hot_streak','spell_warr_sword_and_board','spell_pri_imp_shadowform','spell_dk_butchery','spell_item_unstable_power','spell_item_restless_strength','spell_dru_leader_of_the_pack','spell_pri_aq_3p_bonus','spell_item_persistent_shield','spell_dru_revitalize','spell_dk_death_rune','spell_dk_scent_of_blood_trigger','spell_dk_vendetta','spell_dk_sudden_doom','spell_dk_blade_barrier','spell_dk_rime','spell_dk_wandering_plague','spell_sha_astral_shift_aura','spell_dk_necrosis','spell_sha_static_shock','spell_sha_maelstrom_weapon','spell_sha_ancestral_awakening','spell_rog_deadly_brew','spell_rog_turn_the_tables','spell_rog_cut_to_the_chase','spell_pet_guard_dog','spell_hun_rapid_recuperation_trigger','spell_hun_hunting_party','spell_pal_righteous_vengeance','spell_pal_sheath_of_light','spell_pal_infusion_of_light','spell_pal_judgements_of_the_just','spell_mage_burning_determination','spell_warr_improved_spell_reflection','spell_pet_culling_the_herd','spell_pet_silverback','spell_warl_decimation','spell_sha_frozen_power','spell_pri_body_and_soul','spell_dk_threat_of_thassarian','spell_warl_seduction','spell_mage_combustion','spell_pri_vampiric_embrace','spell_dru_omen_of_clarity','spell_item_alchemists_stone','spell_pal_judgement_of_light_heal','spell_pal_judgement_of_wisdom_mana','spell_twisted_reflection','spell_dru_t3_2p_bonus','spell_dru_t3_8p_bonus','spell_dru_t3_6p_bonus','spell_pal_t3_6p_bonus','spell_pri_t3_4p_bonus','spell_sha_t3_6p_bonus','spell_warr_t3_prot_8p_bonus','spell_item_healing_touch_refund','spell_item_totem_of_flowing_water','spell_item_pendant_of_the_violet_eye','spell_sha_shamanistic_rage','spell_pal_seal_of_vengeance','spell_warl_seed_of_corruption_generic','spell_mark_of_malice','spell_item_mark_of_conquest','spell_sha_windfury_weapon','spell_dru_t4_2p_bonus','spell_pri_t5_heal_2p_bonus','spell_anetheron_vampiric_aura','spell_item_frozen_shadoweave','spell_item_aura_of_madness','spell_pri_item_t6_trinket','spell_dru_item_t6_trinket','spell_sha_item_t6_trinket','spell_pal_item_t6_trinket','spell_item_crystal_spire_of_karabor','spell_item_dementia','spell_item_pet_healing','spell_warl_t4_2p_bonus_shadow','spell_warl_t4_2p_bonus_fire','spell_mage_gen_extra_effects','spell_uk_second_wind','spell_item_commendation_of_kaelthas','spell_item_sunwell_exalted_caster_neck','spell_item_sunwell_exalted_melee_neck','spell_item_sunwell_exalted_tank_neck','spell_item_sunwell_exalted_healer_neck','spell_warl_glyph_of_corruption_nightfall','spell_dk_mark_of_blood','spell_dk_dancing_rune_weapon','spell_dk_unholy_blight','spell_dk_hungering_cold','spell_item_soul_harvesters_charm','spell_rog_turn_the_tables_proc','spell_pal_sacred_shield_dummy','spell_warl_demonic_pact','spell_pal_seal_of_corruption','spell_dru_glyph_of_rejuvenation','spell_dru_glyph_of_shred','spell_dru_glyph_of_rake','spell_dru_glyph_of_innervate','spell_dru_glyph_of_starfire_dummy','spell_pal_glyph_of_holy_light_dummy','spell_pal_glyph_of_divinity','spell_sha_tidal_force_dummy','spell_sha_glyph_of_healing_wave','spell_pri_glyph_of_dispel_magic','spell_mage_glyph_of_ice_block','spell_mage_glyph_of_icy_veins','spell_mage_glyph_of_polymorph','spell_rog_glyph_of_backstab','spell_hun_glyph_of_mend_pet','spell_pri_shadowfiend_death','spell_warr_glyph_of_blocking','spell_dk_glyph_of_scourge_strike','spell_sha_spirit_hunt','spell_hun_kill_command_pet','spell_item_swift_hand_justice_dummy','spell_item_discerning_eye_beast_dummy','spell_mage_imp_mana_gems','spell_gen_vampiric_touch','spell_dk_pvp_4p_bonus','spell_dk_glyph_of_death_grip','spell_dru_savage_defense','spell_sha_glyph_of_earth_shield','spell_sha_glyph_of_totem_of_wrath','spell_warl_glyph_of_life_tap','spell_pal_t8_2p_bonus','spell_sha_t8_elemental_4p_bonus','spell_xt002_321_boombot_aura','spell_sha_t9_elemental_4p_bonus','spell_item_purified_shard_of_the_scale','spell_item_shiny_shard_of_the_scale','spell_dru_t10_balance_4p_bonus','spell_dru_t10_restoration_4p_bonus_dummy','spell_pri_t10_heal_2p_bonus','spell_sha_t10_restoration_4p_bonus','spell_sha_t10_elemental_4p_bonus','spell_warr_item_t10_prot_4p_bonus','spell_item_tiny_abomination_in_a_jar','spell_item_tiny_abomination_in_a_jar_hero','spell_item_deadly_precision_dummy','spell_item_deadly_precision','spell_item_heartpierce','spell_item_heartpierce_hero','spell_item_deathbringers_will_normal','spell_item_deathbringers_will_heroic','spell_item_corpse_tongue_coin','spell_item_corpse_tongue_coin_heroic','spell_putricide_ooze_tank_protection','spell_deathbringer_blood_beast_blood_link','spell_item_petrified_twilight_scale','spell_item_petrified_twilight_scale_heroic','spell_pri_blessed_recovery','spell_mage_blazing_speed','spell_hun_piercing_shots','spell_pal_illumination','spell_rog_overkill','spell_dru_maim_interrupt','spell_gen_petrified_bark','spell_gen_earth_shield_toc','spell_gen_retaliation_toc','spell_gen_overlords_brand','spell_gen_overlords_brand_dot','spell_gen_vampiric_might','spell_gen_mirrored_soul','spell_gen_black_bow_of_the_betrayer','spell_dk_glyph_of_scourge_strike_script'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-10400, 'spell_sha_flametongue_weapon'), +(-11185, 'spell_mage_imp_blizzard'), +(-12834, 'spell_warr_deep_wounds_aura'), +(-13983, 'spell_rog_setup'), +(-15337, 'spell_pri_improved_spirit_tap'), +(-16180, 'spell_sha_imp_water_shield'), +(-18213, 'spell_warl_improved_drain_soul'), +(-20234, 'spell_pal_improved_lay_of_hands'), +(-20335, 'spell_pal_heart_of_the_crusader'), +(-27243, 'spell_warl_seed_of_corruption_dummy'), +(-29441, 'spell_mage_magic_absorption'), + +(-29723, 'spell_warr_extra_proc'), +(-46913, 'spell_warr_extra_proc'), + +(-29834, 'spell_warr_second_wind'), +(-30293, 'spell_warl_soul_leech'), +(-30675, 'spell_sha_lightning_overload'), +(-31244, 'spell_rog_quick_recovery'), +(-31571, 'spell_mage_arcane_potency'), +(-31656, 'spell_mage_empowered_fire'), +(-31785, 'spell_pal_spiritual_attunement'), +(-31871, 'spell_pal_divine_purpose'), +(-31876, 'spell_pal_judgements_of_the_wise'), +(-34497, 'spell_hun_thrill_of_the_hunt'), +(-44404, 'spell_mage_missile_barrage'), +(-44445, 'spell_mage_hot_streak'), +(-46951, 'spell_warr_sword_and_board'), +(-47569, 'spell_pri_imp_shadowform'), +(-48979, 'spell_dk_butchery'), +(-48539, 'spell_dru_revitalize'), + +(-49208, 'spell_dk_death_rune'), +(-49467, 'spell_dk_death_rune'), +(-54639, 'spell_dk_death_rune'), + +(-49004, 'spell_dk_scent_of_blood_trigger'), +(-49015, 'spell_dk_vendetta'), +(-49018, 'spell_dk_sudden_doom'), +(-49182, 'spell_dk_blade_barrier'), +(-49188, 'spell_dk_rime'), +(50526, 'spell_dk_wandering_plague'), -- Damage spell, not talent aura +(-51474, 'spell_sha_astral_shift_aura'), +(-51459, 'spell_dk_necrosis'), +(-51525, 'spell_sha_static_shock'), +(-51556, 'spell_sha_ancestral_awakening'), +(-51625, 'spell_rog_deadly_brew'), +(-51627, 'spell_rog_turn_the_tables'), +(-51664, 'spell_rog_cut_to_the_chase'), +(-53178, 'spell_pet_guard_dog'), +(-53228, 'spell_hun_rapid_recuperation_trigger'), +(-53290, 'spell_hun_hunting_party'), +(-53380, 'spell_pal_righteous_vengeance'), +(-53501, 'spell_pal_sheath_of_light'), +(-53569, 'spell_pal_infusion_of_light'), +(-53695, 'spell_pal_judgements_of_the_just'), +(-54747, 'spell_mage_burning_determination'), +(-59088, 'spell_warr_improved_spell_reflection'), +(-61680, 'spell_pet_culling_the_herd'), +(-62764, 'spell_pet_silverback'), +(-63156, 'spell_warl_decimation'), +(-63373, 'spell_sha_frozen_power'), +(-27811, 'spell_pri_blessed_recovery'), +(-31641, 'spell_mage_blazing_speed'), +(-53234, 'spell_hun_piercing_shots'), +(-20234, 'spell_pal_illumination'), +(58428, 'spell_rog_overkill'), +(44835, 'spell_dru_maim_interrupt'), +(62337, 'spell_gen_petrified_bark'), +(62933, 'spell_gen_petrified_bark'), +(67534, 'spell_gen_earth_shield_toc'), +(65932, 'spell_gen_retaliation_toc'), +(69172, 'spell_gen_overlords_brand'), +(69173, 'spell_gen_overlords_brand_dot'), +(70674, 'spell_gen_vampiric_might'), +(69023, 'spell_gen_mirrored_soul'), +(27522, 'spell_gen_black_bow_of_the_betrayer'), +(40336, 'spell_gen_black_bow_of_the_betrayer'), +(46939, 'spell_gen_black_bow_of_the_betrayer'), +(-64127, 'spell_pri_body_and_soul'), +(-65661, 'spell_dk_threat_of_thassarian'), +(6358, 'spell_warl_seduction'), +(11129, 'spell_mage_combustion'), +(15286, 'spell_pri_vampiric_embrace'), +(16864, 'spell_dru_omen_of_clarity'), +(17619, 'spell_item_alchemists_stone'), +(20185, 'spell_pal_judgement_of_light_heal'), +(20186, 'spell_pal_judgement_of_wisdom_mana'), +(21063, 'spell_twisted_reflection'), +(24658, 'spell_item_unstable_power'), +(24661, 'spell_item_restless_strength'), +(24932, 'spell_dru_leader_of_the_pack'), +(26169, 'spell_pri_aq_3p_bonus'), +(26467, 'spell_item_persistent_shield'), +(28716, 'spell_dru_t3_2p_bonus'), +(28719, 'spell_dru_t3_8p_bonus'), +(28744, 'spell_dru_t3_6p_bonus'), +(28789, 'spell_pal_t3_6p_bonus'), +(28809, 'spell_pri_t3_4p_bonus'), +(28823, 'spell_sha_t3_6p_bonus'), +(28845, 'spell_warr_t3_prot_8p_bonus'), +(28847, 'spell_item_healing_touch_refund'), +(28849, 'spell_item_totem_of_flowing_water'), +(29601, 'spell_item_pendant_of_the_violet_eye'), +(30823, 'spell_sha_shamanistic_rage'), +(31801, 'spell_pal_seal_of_vengeance'), + +(32863, 'spell_warl_seed_of_corruption_generic'), +(36123, 'spell_warl_seed_of_corruption_generic'), +(38252, 'spell_warl_seed_of_corruption_generic'), +(39367, 'spell_warl_seed_of_corruption_generic'), +(44141, 'spell_warl_seed_of_corruption_generic'), +(70388, 'spell_warl_seed_of_corruption_generic'), + +(33493, 'spell_mark_of_malice'), +(33510, 'spell_item_mark_of_conquest'), +(33757, 'spell_sha_windfury_weapon'), +(37288, 'spell_dru_t4_2p_bonus'), +(37295, 'spell_dru_t4_2p_bonus'), +(37594, 'spell_pri_t5_heal_2p_bonus'), +(38196, 'spell_anetheron_vampiric_aura'), +(39372, 'spell_item_frozen_shadoweave'), +(39446, 'spell_item_aura_of_madness'), +(40438, 'spell_pri_item_t6_trinket'), +(40442, 'spell_dru_item_t6_trinket'), +(40463, 'spell_sha_item_t6_trinket'), +(40470, 'spell_pal_item_t6_trinket'), +(40971, 'spell_item_crystal_spire_of_karabor'), +(41404, 'spell_item_dementia'), + +(37381, 'spell_item_pet_healing'), + +(37377, 'spell_warl_t4_2p_bonus_shadow'), +(39437, 'spell_warl_t4_2p_bonus_fire'), +(42770, 'spell_uk_second_wind'), + +(44401, 'spell_mage_gen_extra_effects'), +(48108, 'spell_mage_gen_extra_effects'), +(57761, 'spell_mage_gen_extra_effects'), +(45057, 'spell_item_commendation_of_kaelthas'), +(45481, 'spell_item_sunwell_exalted_caster_neck'), +(45482, 'spell_item_sunwell_exalted_melee_neck'), +(45483, 'spell_item_sunwell_exalted_tank_neck'), +(45484, 'spell_item_sunwell_exalted_healer_neck'), + +(-18094, 'spell_warl_glyph_of_corruption_nightfall'), +(56218, 'spell_warl_glyph_of_corruption_nightfall'), + +(49005, 'spell_dk_mark_of_blood'), +(49028, 'spell_dk_dancing_rune_weapon'), +(49194, 'spell_dk_unholy_blight'), +(51209, 'spell_dk_hungering_cold'), +(52420, 'spell_item_soul_harvesters_charm'), + +(52910, 'spell_rog_turn_the_tables_proc'), +(52914, 'spell_rog_turn_the_tables_proc'), +(52915, 'spell_rog_turn_the_tables_proc'), + +(53601, 'spell_pal_sacred_shield_dummy'), + +(53646, 'spell_warl_demonic_pact'), +(54909, 'spell_warl_demonic_pact'), + +(53736, 'spell_pal_seal_of_corruption'), +(53817, 'spell_sha_maelstrom_weapon'), +(54748, 'spell_mage_burning_determination'), +(54754, 'spell_dru_glyph_of_rejuvenation'), +(54815, 'spell_dru_glyph_of_shred'), +(54821, 'spell_dru_glyph_of_rake'), +(54832, 'spell_dru_glyph_of_innervate'), +(54845, 'spell_dru_glyph_of_starfire_dummy'), +(54937, 'spell_pal_glyph_of_holy_light_dummy'), +(54939, 'spell_pal_glyph_of_divinity'), +(55198, 'spell_sha_tidal_force_dummy'), +(55440, 'spell_sha_glyph_of_healing_wave'), +(55677, 'spell_pri_glyph_of_dispel_magic'), +(56372, 'spell_mage_glyph_of_ice_block'), +(56374, 'spell_mage_glyph_of_icy_veins'), +(56375, 'spell_mage_glyph_of_polymorph'), +(56800, 'spell_rog_glyph_of_backstab'), +(57870, 'spell_hun_glyph_of_mend_pet'), +(57989, 'spell_pri_shadowfiend_death'), +(58375, 'spell_warr_glyph_of_blocking'), +(58642, 'spell_dk_glyph_of_scourge_strike'), +(69961, 'spell_dk_glyph_of_scourge_strike_script'), +(58877, 'spell_sha_spirit_hunt'), +(58914, 'spell_hun_kill_command_pet'), +(59906, 'spell_item_swift_hand_justice_dummy'), +(59915, 'spell_item_discerning_eye_beast_dummy'), + +(37447, 'spell_mage_imp_mana_gems'), +(61062, 'spell_mage_imp_mana_gems'), + +(52723, 'spell_gen_vampiric_touch'), +(60501, 'spell_gen_vampiric_touch'), +(61257, 'spell_dk_pvp_4p_bonus'), +(62259, 'spell_dk_glyph_of_death_grip'), +(62600, 'spell_dru_savage_defense'), +(63279, 'spell_sha_glyph_of_earth_shield'), +(63280, 'spell_sha_glyph_of_totem_of_wrath'), +(63320, 'spell_warl_glyph_of_life_tap'), +(64890, 'spell_pal_t8_2p_bonus'), +(64928, 'spell_sha_t8_elemental_4p_bonus'), +(65032, 'spell_xt002_321_boombot_aura'), +(67228, 'spell_sha_t9_elemental_4p_bonus'), +(69755, 'spell_item_purified_shard_of_the_scale'), +(69739, 'spell_item_shiny_shard_of_the_scale'), +(70723, 'spell_dru_t10_balance_4p_bonus'), +(70664, 'spell_dru_t10_restoration_4p_bonus_dummy'), +(70770, 'spell_pri_t10_heal_2p_bonus'), +(70808, 'spell_sha_t10_restoration_4p_bonus'), +(70817, 'spell_sha_t10_elemental_4p_bonus'), +(70844, 'spell_warr_item_t10_prot_4p_bonus'), + +(71406, 'spell_item_tiny_abomination_in_a_jar'), +(71545, 'spell_item_tiny_abomination_in_a_jar_hero'), + +(71563, 'spell_item_deadly_precision_dummy'), +(71564, 'spell_item_deadly_precision'), + +(71880, 'spell_item_heartpierce'), +(71892, 'spell_item_heartpierce_hero'), + +(71519, 'spell_item_deathbringers_will_normal'), +(71562, 'spell_item_deathbringers_will_heroic'), + +(71634, 'spell_item_corpse_tongue_coin'), +(71640, 'spell_item_corpse_tongue_coin_heroic'), + +(71770, 'spell_putricide_ooze_tank_protection'), +(72176, 'spell_deathbringer_blood_beast_blood_link'), + +(75475, 'spell_item_petrified_twilight_scale'), +(75481, 'spell_item_petrified_twilight_scale_heroic'); + +-- Non scripted auras from `spell_proc_event` +DELETE FROM `spell_proc` WHERE `SpellId` IN (-66799, -63730, -61846, -58872, -57878, -57470, -56636, -56342, -55666, -53709, -53671, -53551, -53527, -53486, -53256, -53234, -53221, -53215, -52795, -52127, -51940, -51692, -51672, -51634, -51562, -51523, -51521, -50880, -49223, -49219, -49149, -49027, -49004, -48988, -48516, -48506, -48496, -48483, -47580, -47516, -47509, -47263, -47258, -47245, -47201, -47195, -46945, -46867, -46854, -45234, -44557, -44449, -44442, -41635, -35541, -35100, -34950, -34935, -34753, -34500, -33881, -33191, -33150, -33142, -33076, -32385, -31833, -31569, -31124, -30881, -30701, -30299, -30160, -29593, -29074, -27811, -20925, -20500, -20210, -20177, -20049, -19572, -19184, -18119, -18096, -17793, -17106, -16958, -16952, -16880, -16487, -16257, -16256, -16176, -14892, -14531, -14186, -13754, -13165, -12966, -12319, -12311, -12298, -12289, -12281, -11255, -11213, -11180, -11095, -9799, -9452, -5952, -324, 6346, 7383, 7434, 8178, 9782, 9784, 12169, 12322, 12999, 13000, 13001, 13002, 13163, 15088, 15128, 15277, 15346, 15600, 16164, 16550, 16620, 16624, 17364, 17495, 20128, 20131, 20132, 20164, 20165, 20166, 20375, 20705, 20784, 20911, 21185, 21882, 21890, 22618, 22648, 23547, 23548, 23551, 23552, 23572, 23578, 23581, 23686, 23688, 23689, 23721, 23920, 24353, 24389, 24905, 25050, 25669, 25899, 26107, 26119, 26128, 26135, 26480, 26605, 27419, 27498, 27521, 27656, 27774, 27787, 28305, 28752, 28802, 28812, 28816, 29150, 29385, 29455, 29501, 29624, 29625, 29626, 29632, 29633, 29634, 29635, 29636, 29637, 29977, 30003, 30937, 31394, 31794, 31904, 32587, 32642, 32734, 32748, 32776, 32777, 32837, 32844, 32885, 33089, 33127, 33297, 33299, 33510, 33648, 33719, 33746, 33759, 33953, 34074, 34080, 34138, 34139, 34258, 34262, 34320, 34355, 34584, 34586, 34598, 34749, 34774, 34783, 34827, 35077, 35080, 35083, 35086, 35121, 36032, 36096, 36111, 36541, 37165, 37170, 37173, 37189, 37193, 37195, 37197, 37213, 37214, 37227, 37237, 37247, 37379, 37384, 37443, 37514, 37516, 37519, 37523, 37528, 37536, 37568, 37600, 37601, 37603, 37655, 37657, 38026, 38031, 38290, 38299, 38326, 38327, 38334, 38347, 38350, 38394, 38857, 39027, 39442, 39443, 39530, 39958, 40407, 40444, 40458, 40475, 40478, 40482, 40485, 40899, 41034, 41260, 41262, 41381, 41393, 41434, 41469, 41989, 42083, 42135, 42136, 42368, 42370, 43443, 43726, 43728, 43737, 43739, 43741, 43745, 43748, 43750, 43819, 44543, 44545, 45054, 45354, 45355, 45469, 45481, 45482, 45483, 45484, 46025, 46092, 46098, 46569, 46662, 46832, 46910, 46911, 47981, 48833, 48835, 48837, 49592, 49622, 50240, 50421, 50781, 51123, 51127, 51128, 51129, 51130, 51346, 51349, 51352, 51359, 51414, 51915, 52020, 52423, 52898, 53386, 53397, 54278, 54646, 54695, 54707, 54738, 54808, 54838, 54841, 54925, 55380, 55381, 55640, 55680, 55681, 55689, 55747, 55768, 55776, 56249, 56355, 56364, 56451, 56816, 56817, 56821, 56841, 57345, 57352, 57907, 57989, 58357, 58364, 58372, 58386, 58442, 58444, 58616, 58620, 58626, 58901, 59176, 59327, 59345, 59630, 59725, 60061, 60063, 60066, 60132, 60170, 60172, 60176, 60221, 60301, 60306, 60317, 60436, 60442, 60473, 60482, 60487, 60490, 60493, 60503, 60519, 60524, 60529, 60537, 60564, 60571, 60572, 60573, 60574, 60575, 60710, 60717, 60719, 60722, 60724, 60726, 60770, 60818, 60826, 61188, 61324, 61356, 61618, 61848, 62114, 62115, 62147, 62459, 63086, 63108, 63251, 63310, 63335, 63611, 64343, 64411, 64415, 64440, 64571, 64714, 64738, 64742, 64752, 64786, 64792, 64824, 64860, 64867, 64882, 64908, 64912, 64914, 64938, 64952, 64955, 64964, 64976, 64999, 65002, 65005, 65007, 65013, 65020, 65025, 66808, 67115, 67151, 67209, 67353, 67356, 67361, 67363, 67365, 67379, 67381, 67384, 67386, 67389, 67392, 67653, 67667, 67670, 67672, 67698, 67702, 67712, 67752, 67758, 67771, 68051, 68160, 70188, 70652, 70727, 70730, 70748, 70756, 70761, 70803, 70807, 70811, 70830, 70841, 70854, 71174, 71176, 71178, 71186, 71191, 71194, 71198, 71214, 71217, 71226, 71228, 71402, 71404, 71540, 71585, 71602, 71606, 71611, 71637, 71642, 71645, 71903, 72413, 72417, 72419, 74396, 75455, 75457, 75465, 75474); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(-66799, 0, 15, 0x00400000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Desolation +(-63730, 0, 6, 0x00000800, 0x00000004, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Serendipity +(-61846, 0, 0, 0x00000000, 0x00000000, 0x00000000, 64, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Aspect of the Dragonhawk +(-58872, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 8259, 0x0, 0, 0, 0, 0), -- Damage Shield +(-57878, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 0, 0), -- Natural Reaction +(-57470, 0, 6, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 15000, 0), -- Renewed Hope +(-56636, 0, 4, 0x00000020, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 6000, 0), -- Taste for Blood +(-56342, 0, 9, 0x00000018, 0x08000000, 0x00024000, 0, 0x0, 0x0, 0, 0x2, 0, 0, 0, 0), -- Lock and Load +(-55666, 0, 15, 0x00000001, 0x08000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Desecration +(-53709, 2, 10, 0x00004000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Shield of the Templar +(-53671, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Judgements of the Pure +(-53551, 0, 10, 0x00001000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sacred Cleansing +(-53527, 1, 10, 0x00000000, 0x00000000, 0x00000004, 1024, 0x0, 0x2, 1, 0x0, 0, 100, 0, 0), -- Divine Guardian +(-53486, 0, 10, 0x00800000, 0x00028000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- The Art of War +(-53256, 0, 9, 0x00000800, 0x00800001, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Cobra Strikes +(-53234, 0, 9, 0x00020000, 0x00000001, 0x00000001, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Piercing Shots +(-53221, 0, 9, 0x00000000, 0x00000001, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Steady Shot +(-53215, 0, 9, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Wild Quiver +(-52795, 0, 6, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Borrowed Time +(-52127, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Water Shield +(-51940, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 20, 0, 0), -- Earthliving Weapon (Passive) +(-51692, 0, 8, 0x00000204, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Waylay +(-51672, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 1000, 0), -- Unfair Advantage +(-51634, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Focused Attacks +(-51562, 0, 11, 0x00000100, 0x00000000, 0x00000010, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Tidal Waves +(-51523, 0, 11, 0x00000000, 0x00000001, 0x00000000, 65536, 0x0, 0x2, 0, 0x0, 0, 50, 0, 0), -- Earthen Power +(-51521, 0, 11, 0x00000000, 0x01000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Stormstrike +(-50880, 0, 15, 0x00000000, 0x04000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Icy Talons +(-49223, 0, 15, 0x00000011, 0x08020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Dirge +(-49219, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Blood-Caked Blade +(-49149, 0, 15, 0x00000006, 0x00020002, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Chill of the Grave +(-49027, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 20000, 0), -- Bloodworms +(-49004, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 51, 0x0, 0, 0, 0, 0), -- Scent of Blood +(-48988, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Bloody Vengeance +(-48516, 0, 7, 0x00000005, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Eclipse +(-48506, 0, 7, 0x00000005, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Earth and Moon +(-48496, 0, 7, 0x00000060, 0x02000002, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Living Seed +(-48483, 0, 7, 0x00008800, 0x00000440, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Infected Wounds +(-47580, 0, 6, 0x00000000, 0x00000000, 0x00000040, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Pain and Suffering +(-47516, 0, 6, 0x00001800, 0x00010000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Grace +(-47509, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Divine Aegis +(-47263, 32, 5, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 20000, 0), -- Torture +(-47258, 0, 5, 0x00000000, 0x00800000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Backdraft +(-47245, 0, 5, 0x00000002, 0x00000000, 0x00000000, 262144, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Molten Core +(-47201, 0, 5, 0x00004009, 0x00040000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Everlasting Affliction +(-47195, 0, 5, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Eradication +(-46945, 0, 4, 0x00000000, 0x00010000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Safeguard +(-46867, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Wrecking Crew +(-46854, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Trauma +(-45234, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Focused Will +(-44557, 0, 3, 0x00000020, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Enduring Winter +(-44449, 0, 3, 0x20E21277, 0x00019048, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Burnout +(-44442, 0, 3, 0x00800000, 0x00000040, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 1000, 0), -- Firestarter +(-41635, 0, 0, 0x00000000, 0x00000000, 0x00000000, 664232, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Prayer of Mending +(-35541, 0, 0, 0x00000000, 0x00000000, 0x00000000, 8388608, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Combat Potency +(-35100, 0, 9, 0x00001000, 0x00000000, 0x00000001, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Concussive Barrage +(-34950, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Go for the Throat +(-34935, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 8000, 0), -- Backlash +(-34753, 0, 6, 0x00001800, 0x00000004, 0x00001000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Holy Concentration +(-34500, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Expose Weakness +(-33881, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Natural Perfection +(-33191, 0, 6, 0x00008000, 0x00000400, 0x00000040, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Misery +(-33150, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Surge of Light +(-33142, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Blessed Resilience +(-33076, 0, 0, 0x00000000, 0x00000000, 0x00000000, 664232, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Prayer of Mending +(-32385, 0, 5, 0x00000001, 0x00040000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Shadow Embrace +(-31833, 0, 10, 0x80000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Light's Grace +(-31569, 0, 3, 0x00010000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Blink +(-31124, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blade Twisting +(-30881, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Nature's Guardian +(-30701, 28, 0, 0x00000000, 0x00000000, 0x00000000, 664232, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), -- Elemental Absorption +(-30299,126, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Nether Protection +(-30160, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Elemental Devastation +(-29593, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 112, 0x0, 0, 0, 0, 0), -- Improved Defensive Stance +(-29074, 20, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Master of Elements +(-27811, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Blessed Recovery +(-20925, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Holy Shield +(-20500, 0, 4, 0x10000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Berserker Rage +(-20210, 0, 10, 0xC0000000, 0x00010000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Illumination +(-20177, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Reckoning +(-20049, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Vengeance +(-19572, 0, 9, 0x00800000, 0x00000000, 0x00000000, 262144, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Improved Mend Pet +(-19184, 0, 9, 0x00000010, 0x00002000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Entrapment +(-18119, 0, 5, 0x00000000, 0x00800000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Aftermath +(-18096, 0, 5, 0x00000100, 0x00800000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Pyroclasm +(-17793, 0, 5, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Shadow Bolt +(-17106, 0, 7, 0x00080000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Intensity +(-16958, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Primal Fury +(-16952, 0, 7, 0x00039000, 0x00000400, 0x00040000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Blood Frenzy +(-16880, 72, 7, 0x00000067, 0x03800002, 0x00000000, 0, 0x0, 0x3, 2, 0x0, 0, 0, 0, 0), -- Nature's Grace +(-16487, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Blood Craze +(-16257, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Flurry +(-16256, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Flurry +(-16176, 0, 11, 0x000001C0, 0x00000000, 0x00000010, 0, 0x2, 0x2, 2, 0x0, 0, 0, 0, 0), -- Ancestral Healing +(-14892, 0, 6, 0x10001E00, 0x00010004, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Inspiration +(-14531, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Martyrdom +(-14186, 0, 8, 0x40800508, 0x00000006, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Seal Fate +(-13754, 0, 8, 0x00000010, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Kick +(-13165, 0, 0, 0x00000000, 0x00000000, 0x00000000, 64, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Aspect of the Hawk +(-12966, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Flurry +(-12319, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Flurry +(-12311, 0, 4, 0x00000800, 0x00000001, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Gag Order +(-12298, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 112, 0x0, 0, 0, 0, 0), -- Shield Specialization +(-12289, 0, 4, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Hamstring +(-12281, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 6000, 0), -- Sword Specialization +(-11255, 0, 3, 0x00004000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Counterspell +(-11213, 0, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Arcane Concentration +(-11180, 16, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Winter's Chill +(-11095, 0, 3, 0x00000010, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Scorch +(-9799, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Eye for an Eye +(-9452, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 3, 0, 0, 0), -- Vindication +(-5952, 0, 8, 0x00000000, 0x00000001, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Throwing Specialization +(-324, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Lightning Shield +(6346, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 256, 0x0, 0, 0, 0, 0), -- Fear Ward +(7383, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 256, 0x0, 0, 0, 0, 0), -- Water Bubble +(7434, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Fate Rune of Unsurpassed Vigor +(8178, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Grounding Totem Effect +(9782, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Mithril Shield Spike +(9784, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Iron Shield Spike +(12169, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Shield Block +(12322, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 3, 0, 0, 0), -- Unbridled Wrath +(12999, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 6, 0, 0, 0), -- Unbridled Wrath +(13000, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 9, 0, 0, 0), -- Unbridled Wrath +(13001, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 12, 0, 0, 0), -- Unbridled Wrath +(13002, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 15, 0, 0, 0), -- Unbridled Wrath +(13163, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 0, 0), -- Aspect of the Monkey +(15088, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Flurry +(15128, 4, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Mark of Flames +(15277, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Seal of Reckoning +(15346, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0), -- Seal of Reckoning +(15600, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 1, 0, 0, 0), -- Hand of Justice +(16164, 28, 0, 0x00000000, 0x00000000, 0x00000000, 65536, 0x0, 0x1, 2, 0x0, 0, 0, 0, 0), -- Elemental Focus (CAST phase for travel-time spells) +(16550, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Bonespike +(16620, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 30000, 0), -- Proc Self Invulnerability +(16624, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Thorium Shield Spike +(17364, 8, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Stormstrike +(17495, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Crest of Retribution +(20128, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Redoubt +(20131, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Redoubt +(20132, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Redoubt +(20164, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 5, 0, 0, 0), -- Seal of Justice +(20165, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 20, 0, 0, 0), -- Seal of Light +(20166, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 12, 0, 0, 0), -- Seal of Wisdom +(20375, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 1000, 0), -- Seal of Command +(20705, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Power Shield 500 +(20784, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Tamed Pet Passive 07 (DND) +(21185, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Spinal Reaper +(21882, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Judgement Smite +(21890, 0, 4, 0x2A764EEF, 0x0000036C, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Warrior's Wrath +(22618, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Force Reactive Disk +(22648, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Call of Eskhandar +(23547, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 32, 0x0, 0, 0, 0, 0), -- Parry +(23548, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Parry +(23551, 0, 11, 0x000000C0, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Lightning Shield +(23552, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Lightning Shield +(23572, 0, 11, 0x000000C0, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Mana Surge +(23578, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 2, 0, 0, 0), -- Expose Weakness +(23581, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 2, 0, 0, 0), -- Bloodfang +(23686, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 2, 0, 0, 0), -- Lightning Strike +(23688, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Aura of the Blue Dragon +(23689, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 4, 0, 0, 0), -- Heroism +(23721, 0, 9, 0x00000800, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Arcane Infused +(23920, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(24353, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Primal Instinct +(24389, 4, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Chaos Fire +(24905, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 15, 0, 0, 0), -- Moonkin Form (Passive) +(25050, 4, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Mark of Flames +(25669, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 1, 0, 0, 0), -- Decapitate +(25899, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 112, 0x0, 0, 0, 0, 0), -- Greater Blessing of Sanctuary +(26107, 0, 7, 0x00800000, 0x10000080, 0x00000000, 0, 0x0, 0x2, 116, 0x0, 0, 0, 0, 0), -- Symbols of Unending Life Finisher Bonus +(26119, 0, 10, 0x90100003, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Stormcaller Spelldamage Bonus +(26128, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 8, 0x0, 0, 0, 0, 0), -- Enigma Resist Bonus +(26135, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0), -- Battlegear of Eternal Justice +(26480, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 10, 0, 0, 0), -- Badge of the Swarmguard (AC #16777) +(26605, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x2, 0, 0, 0, 0), -- Bloodcrown +(27419, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 3, 0, 0, 0), -- Warrior's Resolve +(27498, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 3, 0, 0, 0), -- Crusader's Wrath +(27521, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 15000, 0), -- Mana Restore +(27656, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Flame Lash +(27774, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- The Furious Storm +(27787, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 1, 0, 0, 0), -- Rogue Armor Energize (AC #11048) +(28305, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Mana Leech +(28752, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Adrenaline Rush +(28802, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Epiphany +(28812, 0, 8, 0x02000006, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Head Rush +(28816, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 3, 0, 0, 0), -- Invigorate +(29150, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Electric Discharge +(29385, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 7, 0, 1000, 0), -- Seal of Command +(29455, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Felsteel Shield Spike +(29501, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Frost Arrow +(29624, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Searing Arrow +(29625, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Flaming Cannonball +(29626, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Shadow Bolt +(29632, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Shadow Shot +(29633, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Fire Blast +(29634, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Quill Shot +(29635, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Flaming Shell +(29636, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Venom Shot +(29637, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Keeper's Sting +(29977, 4, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Combustion +(30003, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Sheen of Zanza +(30937, 32, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Mark of Shadow +(31394, 32, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Mark of Shadow +(31794, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Focused Mind +(31904, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Holy Shield +(32587, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Shield Block +(32642, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Spore Cloud +(32734, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Earth Shield +(32748, 0, 8, 0x00000000, 0x00000001, 0x00000000, 320, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Deadly Throw Interrupt +(32776, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Redoubt +(32777, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Holy Shield +(32837, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Spell Focus Trigger +(32844, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 2, 0, 0, 0), -- Lesser Heroism +(32885, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Infuriate +(33089, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Vigilance of the Colossus +(33127, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 7, 0, 1000, 0), -- Seal of Command +(33297, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Spell Haste Trinket +(33299, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Coilfang Slave Pens Lvl 70 Boss3a Caster Trinket +(33510, 0, 0, 0x00000000, 0x00000000, 0x00000000, 340, 0x0, 0x2, 0, 0x0, 0, 15, 25000, 0), -- Health Restore (AC #16551) +(33648, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Reflection of Torment +(33719, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Perfect Spell Reflection +(33746, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Essence Infused Mushroom +(33759, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Power Infused Mushroom +(33953, 0, 0, 0x00000000, 0x00000000, 0x00000000, 17408, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Essence of Life +(34074, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Aspect of the Viper +(34080, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 32, 0x0, 0, 0, 0, 0), -- Riposte Stance +(34138, 0, 11, 0x00000080, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Totem of the Third Wind +(34139, 0, 10, 0x40000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Libram of Justice +(34258, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0), -- Justice +(34262, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0), -- Mercy +(34320, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Call of the Nexus +(34355, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Poison Shield +(34584, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 30000, 0), -- Love Struck +(34586, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0,1.5, 0, 0, 0), -- Romulo's Poison +(34598, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Karazhan Caster Robe +(34749, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 8, 0x2, 0, 0, 0, 0), -- Recurring Power +(34774, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0,1.5, 0, 20000, 0), -- Magtheridon Melee Trinket +(34783, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(34827, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Water Shield +(35077, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 55000, 0), -- Band of the Eternal Defender +(35080, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 1, 0, 55000, 0), -- Band of the Eternal Champion +(35083, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 55000, 0), -- Band of the Eternal Sage +(35086, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 55000, 0), -- Band of the Eternal Restorer +(35121, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Nether Power +(36032, 0, 3, 0x00001000, 0x00008000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Arcane Blast +(36096, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(36111, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- World Breaker +(36541, 4, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0), -- Curse of Burning Shadows +(37165, 0, 8, 0x00200400, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Haste +(37170, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 1, 0, 0, 0), -- Free Finisher Chance +(37173, 0, 8, 0x2CBC0598, 0x00000106, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 25000, 0), -- Armor Penetration +(37189, 0, 10, 0xC0000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 60000, 0), -- Recuced Holy Light Cast Time +(37193, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Infused Shield +(37195, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0), -- Judgement Group Heal +(37197, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Spell Damage +(37213, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Mana Cost Reduction +(37214, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Energized +(37227, 0, 11, 0x000001C0, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 60000, 0), -- Improved Healing Wave +(37237, 0, 11, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Lightning Bolt Discount +(37247, 8, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Regain Mana +(37379, 32, 5, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Flameshadow +(37384, 0, 5, 0x00000001, 0x00000040, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Corruption and Immolate +(37443, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Crit Bonus Damage +(37514, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 32, 0x0, 0, 0, 0, 0), -- Blade Turning +(37516, 0, 4, 0x00000400, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Revenge Bonus +(37519, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 48, 0x0, 0, 0, 0, 0), -- Rage Bonus +(37523, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Reinforced Shield +(37528, 0, 4, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Overpower Bonus +(37536, 0, 4, 0x00010000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Improved Battle Shout +(37568, 0, 6, 0x00000800, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Greater Heal Discount +(37600, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Offensive Discount +(37601, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Relentlessness +(37603, 0, 6, 0x00008000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Shadow Word Pain Damage +(37655, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Bonus Mana Regen +(37657, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 2500, 0), -- Lightning Capacitor +(38026, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 256, 0x0, 0, 0, 0, 0), -- Viscous Shield +(38031, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Shield Block +(38290, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0,1.6, 0, 0, 0), -- Santos' Blessing +(38299, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 12000, 0), -- HoTs on Heals +(38326, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Crit Threat Reduction Melee +(38327, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0), -- Crit Threat Reduction Spell +(38334, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Proc Mana Regen +(38347, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Crit Proc Spell Damage +(38350, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Crit Proc Heal +(38394, 0, 5, 0x00000006, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Dot Heals +(38857, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Spell Ground +(39027, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Poison Shield +(39442, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 1, 0x0, 0, 0, 0, 0), -- Aura of Wrath +(39443, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Aura of Wrath +(39530, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Focus +(39958, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0,0.7, 0, 40000, 0), -- Skyfire Swiftness +(40407, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 6, 0, 0, 0), -- Illidan Tank Shield (AC Mangos legacy) +(40444, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Black Temple Tank Trinket +(40458, 0, 4, 0x02000000, 0x00000601, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Warrior Tier 6 Trinket +(40475, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 3, 0, 0, 0), -- Black Temple Melee Trinket +(40478, 0, 5, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Warlock Tier 6 Trinket +(40482, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Mage Tier 6 Trinket +(40485, 0, 9, 0x00000000, 0x00000001, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Hunter Tier 6 Trinket +(40899, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 1, 0, 0, 0), -- Felfire Proc +(41034, 126, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 1024, 0x0, 0, 0, 0, 0), -- Spell Absorption +(41260, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Aviana's Purpose +(41262, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Aviana's Will +(41381, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 256, 0x0, 0, 0, 0, 0), -- Shell of Life +(41393, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 32, 0x0, 0, 0, 0, 0), -- Riposte +(41434, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 2, 0, 45000, 0), -- The Twin Blades of Azzinoth +(41469, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 7, 0, 1000, 0), -- Seal of Command +(41989, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0,1.5, 0, 0, 0), -- Fists of Fury (AC #21454) +(42083, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Fury of the Crashing Waves +(42135, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 90000, 0), -- Lesser Rune of Warding +(42136, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 90000, 0), -- Greater Rune of Warding +(42368, 0, 10, 0x40000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Merciless Libram of Justice +(42370, 0, 11, 0x00000080, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Merciless Totem of the Third WInd +(43443, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(43726, 0, 10, 0x40000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Vengeful Libram of Justice +(43728, 0, 11, 0x00000080, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Vengeful Totem of Third WInd +(43737, 0, 7, 0x00000000, 0x00000440, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 10000, 0), -- Primal Instinct +(43739, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Lunar Grace +(43741, 0, 10, 0x80000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Light's Grace +(43745, 0, 10, 0x00000000, 0x00000200, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Crusader's Command +(43748, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Elemental Strength +(43750, 0, 11, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Energized +(43819, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Lucidity +(44543, 0, 3, 0x00100220, 0x00001000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 7, 0, 0), -- Fingers of Frost +(44545, 0, 3, 0x00100220, 0x00001000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 15, 0, 0), -- Fingers of Frost +(45054, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 15000, 0), -- Augment Pain +(45354, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Sunwell Dungeon Melee Trinket +(45355, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - T7 Melee Trinket Base +(45469, 0, 15, 0x00000010, 0x00000000, 0x00000000, 16, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Death Strike +(45481, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Sunwell Exalted Caster Neck +(45482, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Sunwell Exalted Melee Neck +(45483, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Sunwell Exalted Tank Neck +(45484, 0, 0, 0x00000000, 0x00000000, 0x00000000, 16384, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Sunwell Exalted Healer Neck +(46025, 32, 6, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blackout +(46092, 0, 10, 0x40000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Brutal Libram of Justice +(46098, 0, 11, 0x00000080, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Brutal Totem of Third WInd +(46569, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Sunwell Exalted Caster Neck +(46662, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 20000, 0), -- Deathfrost +(46832, 0, 7, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Moonkin Starfire Bonus +(46910, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0,5.5, 0, 0, 0), -- Furious Attacks +(46911, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0,7.5, 0, 0, 0), -- Furious Attacks +(47981, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(48833, 0, 7, 0x00000000, 0x00000440, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Primal Instinct +(48835, 0, 10, 0x00800000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0), -- Justice +(48837, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Elemental Tenacity +(49592, 0, 0, 0x00000000, 0x00000000, 0x00000000, 8528552, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Temporal Rift +(49622, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Bonus Mana Regen +(50240, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 0, 0), -- Evasive Maneuvers +(50421, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Scent of Blood +(50781, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 6000, 0), -- Fate Rune of Primal Energy +(51123, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 1, 0, 0, 0), -- Killing Machine +(51127, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 2, 0, 0, 0), -- Killing Machine +(51128, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 4, 0, 0, 0), -- Killing Machine +(51129, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 6, 0, 0, 0), -- Killing Machine +(51130, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 8, 0, 0, 0), -- Killing Machine +(51346, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Venture Company Beatdown! +(51349, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Venture Company Beatdown +(51352, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Venture Company Beatdown! +(51359, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 0, 0, 10000, 0), -- Venture Company Beatdown +(51414, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 45000, 0), -- Venomous Breath Aura +(51915, 0, 0, 0x00000000, 0x00000000, 0x00000000,16777216, 0x0, 0x0, 0, 0x0, 0, 100, 600000, 0), -- Undying Resolve +(52020, 0, 7, 0x00008000, 0x00100000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Snap and Snarl +(52423, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 32, 0x0, 0, 0, 0, 0), -- Retaliation +(52898, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2, 0x0, 0, 0, 0, 0), -- Spell Damping +(53386, 0, 15, 0x82127F27, 0x000001BF, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0), -- Cinderglacier +(53397, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Invigoration +(54278, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Empowered Imp +(54646, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Focus Magic +(54695, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Death Knight's Anguish Base +(54707, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Sonic Awareness (DND) +(54738, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Star of Light +(54808, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 60000, 0), -- Sonic Shield +(54838, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Purified Spirit +(54841, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 2500, 0), -- Thunder Capacitor +(54925, 2, 10, 0x00000000, 0x00000200, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Seal of Command +(55380, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 40000, 0), -- Skyflare Swiftness (Thundering Skyflare Diamond) +(55381, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 15000, 0), -- Mana Restore +(55640, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Lightweave Embroidery +(55680, 0, 6, 0x00000200, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Prayer of Healing +(55681, 0, 6, 0x00008000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Shadow Word: Pain +(55689, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Glyph of Shadow +(55747, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Argent Fury +(55768, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Darkglow Embroidery +(55776, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Swordguard Embroidery +(56249, 0, 5, 0x00000000, 0x00000000, 0x00000400, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Felhunter +(56355, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 64, 0x0, 0, 0, 0, 0), -- Titanium Shield Spike +(56364, 0, 3, 0x00000000, 0x01000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Remove Curse +(56451, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 3000, 0), -- Earth Shield +(56816, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 48, 0x0, 0, 0, 0, 0), -- Rune Strike +(56817, 0, 15, 0x00000000, 0x20000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Rune strike proc (SERVERSIDE) +(56821, 0, 8, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Glyph of Sinister Strike +(56841, 0, 9, 0x00000800, 0x00000000, 0x00000000, 256, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Arcane Shot +(57345, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Darkmoon Card: Greatness +(57352, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Darkmoon Card: Death +(57907, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Increased Spirit +(57989, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Shadowfiend Death +(58357, 0, 4, 0x00000040, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Glyph of Heroic Strike +(58364, 0, 4, 0x00000400, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Revenge +(58372, 0, 4, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Hamstring +(58386, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 32, 0x0, 0, 0, 0, 0), -- Glyph of Overpower +(58442, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 15000, 0), -- Airy Pale Ale +(58444, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 5000, 0), -- Worg Tooth Oatmeal Stout +(58616, 0, 15, 0x01000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Heart Strike +(58620, 0, 15, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Chains of Ice +(58626, 0, 15, 0x02000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Death Grip +(58901, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Tears of Anguish +(59176, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 2, 0x0, 0, 0, 0, 0), -- Spell Damping +(59327, 0, 15, 0x08000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Rune Tap +(59345, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Chagrin +(59630, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 35000, 0), -- Black Magic +(59725, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 2048, 0x0, 0, 0, 0, 0), -- Spell Reflection +(60061, 0, 0, 0x00000000, 0x00000000, 0x00000000, 294912, 0x2, 0x0, 0, 0x0, 0, 0, 45000, 0), -- Flow of Time +(60063, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Now is the Time! +(60066, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Rage of the Unraveller +(60132, 0, 15, 0x00000010, 0x08020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Oblit/Scourge Strike Runic Power Up +(60170, 0, 5, 0x00000006, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Corruption Triggers Crit +(60172, 0, 5, 0x00040000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Life Tap Bonus Spirit +(60176, 0, 4, 0x00000020, 0x00000010, 0x00000000, 262144, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Bleed Cost Reduction +(60221, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0), -- Essence of Gossamer +(60301, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Meteorite Whetstone +(60306, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Vestige of Haldor +(60317, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Signet of Edward the Odd +(60436, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Grim Toll +(60442, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Bandit's Insignia +(60473, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Forge Ember +(60482, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Pendulum of Telluric Currents +(60487, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 15000, 0), -- Extract of Necromatic Power +(60490, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Embrace of the Spider +(60493, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Dying Curse +(60503, 1, 4, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Taste for Blood +(60519, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Spark of Life +(60524, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Majestic Dragon Figurine +(60529, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Forethought Talisman +(60537, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Soul of the Dead +(60564, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Savage Gladiator's Totem of Survival +(60571, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Hateful Gladiator's Totem of Survival +(60572, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Deadly Gladiator's Totem of Survival +(60573, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 4 Gladiator's Totem of Survival +(60574, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 5 Gladiator's Totem of Survival +(60575, 0, 11, 0x90100000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 6 Gladiator's Totem of Survival +(60710, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Savage Gladiator's Idol of Steadfastness +(60717, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Hateful Gladiator's Idol of Steadfastness +(60719, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Deadly Gladiator's Idol of Steadfastness +(60722, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 4 Gladiator's Idol of Steadfastness +(60724, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 5 Gladiator's Idol of Steadfastness +(60726, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- LK Arena 6 Gladiator's Idol of Steadfastness +(60770, 0, 11, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Totem of the Elemental Plane +(60818, 0, 10, 0x00000000, 0x00000200, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Libram of Reciprocation +(60826, 0, 15, 0x01400000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Sigil of Haunted Dreams +(61188, 0, 5, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Chaotic Mind +(61324, 0, 10, 0x00000000, 0x00020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Justice +(61356, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 90000, 0), -- Invigorating Earthsiege Diamond Passive +(61618, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Tentacles +(61848, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 0, 0), -- Aspect of the Dragonhawk +(62114, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Flow of Knowledge +(62115, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Strength of the Titans +(62147, 0, 15, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Icy Touch Defense Increase +(62459, 0, 15, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Chains of Ice Frost Rune Refresh +(63086, 0, 9, 0x00000000, 0x00000000, 0x00010000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Raptor Strike +(63108, 0, 5, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Siphon Life +(63251, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Glory of the Jouster +(63310, 0, 5, 0x00000000, 0x00010000, 0x00000000, 65536, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Shadowflame +(63335, 0, 15, 0x00000000, 0x00000002, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Glyph of Howling Blast +(63611, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x1, 0, 0, 0, 0), -- Improved Blood Presence +(64343, 0, 3, 0x00000002, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Impact +(64411, 0, 0, 0x00000000, 0x00000000, 0x00000000, 279552, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Blessing of Ancient Kings +(64415, 0, 0, 0x00000000, 0x00000000, 0x00000000, 279552, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Val'anyr Hammer of Ancient Kings - Equip Effect +(64440, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 32, 0x0, 0, 0, 20000, 0), -- Blade Warding +(64571, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 10000, 0), -- Blood Draining +(64714, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Flame of the Heavens +(64738, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Show of Faith +(64742, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Pandora's Plea +(64752, 0, 7, 0x00001000, 0x00000100, 0x00200000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T8 Feral 2P Bonus +(64786, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Comet's Trail +(64792, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Blood of the Old God +(64824, 0, 7, 0x00200000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T8 Balance 4P Bonus +(64860, 0, 9, 0x00000000, 0x00000001, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Hunter T8 4P Bonus +(64867, 0, 3, 0x20000021, 0x00001000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Mage T8 2P Bonus +(64882, 0, 10, 0x00000000, 0x00100000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T8 Protection 4P Bonus +(64908, 0, 6, 0x00002000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Priest T8 Shadow 4P Bonus +(64912, 0, 6, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Priest T8 Healer 4P Bonus +(64914, 0, 8, 0x00010000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Rogue T8 2P Bonus +(64938, 0, 4, 0x00200040, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 0, 0), -- Item - Warrior T8 Melee 2P Bonus +(64952, 0, 7, 0x00000000, 0x00000440, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T8 Feral Relic +(64955, 0, 10, 0x00000000, 0x00000040, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T8 Protection Relic +(64964, 0, 15, 0x00000000, 0x20000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Death Knight T8 Tank Relic +(64976, 0, 4, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Juggernaut +(64999, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x4, 0, 0, 0, 0), -- Meteoric Inspiration +(65002, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Bonus Mana Regen +(65005, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Alacrity of the Elements +(65007, 0, 0, 0x00000000, 0x00000000, 0x00000000, 81920, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Eye of the Broodmother +(65013, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Pyrite Infusion +(65020, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Mjolnir Runestone +(65025, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Dark Matter +(66808, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Meteor Fists +(67115, 0, 15, 0x01400000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Death Knight T9 Melee 2P Bonus +(67151, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Hunter T9 4P Bonus (Steady Shot) +(67209, 1, 8, 0x00100000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Rogue T9 2P Bonus (Rupture) +(67353, 0, 7, 0x00008000, 0x00100500, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T9 Feral Relic (Lacerate, Swipe, Mangle, and Shred) +(67356, 8, 7, 0x00000010, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T9 Restoration Relic (Rejuvenation) +(67361, 0, 7, 0x00000002, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T9 Balance Relic (Moonfire) +(67363, 0, 10, 0x80000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 10000, 0), -- Item - Paladin T9 Holy Relic (Judgement) +(67365, 0, 10, 0x00000000, 0x00000800, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 6000, 0), -- Item - Paladin T9 Retribution Relic (Seal of Vengeance) +(67379, 0, 10, 0x00000000, 0x00040000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T9 Protection Relic (Hammer of The Righteous) +(67381, 0, 15, 0x00000000, 0x20000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 10000, 0), -- Item - Death Knight T9 Tank Relic (Rune Strike) +(67384, 0, 15, 0x00000010, 0x08020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 80, 10000, 0), -- Item - Death Knight T9 Melee Relic (Rune Strike) +(67386, 0, 11, 0x00000001, 0x00000000, 0x00000000, 65536, 0x0, 0x1, 0, 0x0, 0, 0, 6000, 0), -- Item - Shaman T9 Elemental Relic (Lightning Bolt) +(67389, 0, 11, 0x00000100, 0x00000000, 0x00000000, 16384, 0x0, 0x1, 0, 0x0, 0, 0, 8000, 0), -- Item - Shaman T9 Restoration Relic (Chain Heal) +(67392, 0, 11, 0x00000000, 0x00000000, 0x00000004, 16, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T9 Enhancement Relic (Lava Lash) +(67653, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4194344, 0x1, 0x0, 0, 0x0, 0, 0, 45000, 0), -- Coliseum 5 Tank Trinket +(67667, 0, 0, 0x00000000, 0x00000000, 0x00000000, 16384, 0x2, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Coliseum 5 Healer Trinket +(67670, 0, 0, 0x00000000, 0x00000000, 0x00000000, 65536, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Coliseum 5 CasterTrinket +(67672, 0, 0, 0x00000000, 0x00000000, 0x00000000, 8388948, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Coliseum 5 Melee Trinket +(67698, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Item - Coliseum 25 Normal Healer Trinket +(67702, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Coliseum 25 Normal Melee Trinket +(67712, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x2, 2, 0x0, 0, 0, 2000, 0), -- Item - Coliseum 25 Normal Caster Trinket +(67752, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Item - Coliseum 25 Heroic Healer Trinket +(67758, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x2, 2, 0x0, 0, 0, 2000, 0), -- Item - Coliseum 25 Heroic Caster Trinket +(67771, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Coliseum 25 Heroic Melee Trinket +(68051, 1, 4, 0x00000004, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Overpower Ready! +(68160, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Meteor Fists +(70188, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 16, 0x0, 0, 0, 0, 0), -- Cloak of Darkness +(70652, 0, 15, 0x00000008, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Death Knight T10 Tank 4P Bonus +(70727, 0, 9, 0x00000001, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Hunter T10 2P Bonus +(70730, 0, 9, 0x00004000, 0x00001000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Hunter T10 4P Bonus +(70748, 0, 3, 0x00000000, 0x00200000, 0x00000000, 1024, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Mage T10 4P Bonus +(70756, 0, 10, 0x00000000, 0x00010000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T10 Holy 4P Bonus +(70761, 0, 10, 0x00000000, 0x00000000, 0x00000001, 1024, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T10 Protection 4P Bonus +(70803, 0, 8, 0x003E0000, 0x00000008, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Rogue T10 4P Bonus +(70807, 0, 11, 0x00000000, 0x00000000, 0x00000010, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Restoration 2P Bonus +(70811, 0, 11, 0x00000003, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Elemental 2P Bonus +(70830, 0, 11, 0x00000000, 0x00020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Enhancement 2P Bonus +(70841, 0, 5, 0x00000004, 0x00000100, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Warlock T10 4P Bonus +(70854, 0, 4, 0x00000000, 0x00000010, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Warrior T10 Melee 2P Bonus +(71174, 1, 7, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T10 Feral Relic (Rake and Lacerate) +(71176, 0, 7, 0x00200002, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T10 Balance Relic (Moonfire and Insect Swarm) +(71178, 0, 7, 0x00000010, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Druid T10 Restoration Relic (Rejuvenation) +(71186, 0, 10, 0x00000000, 0x00008000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T10 Retribution Relic (Crusader Strike) +(71191, 0, 10, 0x00000000, 0x00010000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T10 Holy Relic (Holy Shock) +(71194, 0, 10, 0x00000000, 0x00100000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Paladin T10 Protection Relic (Shield of Righteousness) +(71198, 4, 11, 0x10000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Elemental Relic (Shocks) +(71214, 0, 11, 0x00000000, 0x00000010, 0x00000000, 16, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Enhancement Relic (Stormstrike) +(71217, 0, 11, 0x00000000, 0x00000000, 0x00000010, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Shaman T10 Restoration Relic (Riptide) +(71226, 0, 15, 0x00000010, 0x08020000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Death Knight T10 DPS Relic (Obliterate, Scourge Strike, Death Strike) +(71228, 0, 15, 0x00000000, 0x20000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0), -- Item - Death Knight T10 Tank Relic (Runestrike) +(71402, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 10 Normal Melee Trinket +(71404, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 2, 0x0, 0, 0, 45000, 0), -- Item - Icecrown Dungeon Melee Trinket +(71540, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 10 Heroic Melee Trinket +(71585, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 25 Emblem Healer Trinket +(71602, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 25 Normal Caster Trinket 1 Base +(71606, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 100000, 0), -- Item - Icecrown 25 Normal Caster Trinket 2 +(71611, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 25 Normal Healer Trinket 2 +(71637, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 100000, 0), -- Item - Icecrown 25 Heroic Caster Trinket 2 +(71642, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 25 Heroic Healer Trinket 2 +(71645, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Item - Icecrown 25 Heroic Caster Trinket 1 Base +(71903, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 12, 0, 0, 0), -- Item - Shadowmourne Legendary +(72413, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 10, 60000, 0), -- Item - Icecrown Reputation Ring Melee +(72417, 0, 0, 0x00000000, 0x00000000, 0x00000000, 327680, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Item - Icecrown Reputation Ring Caster Trigger +(72419, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 60000, 0), -- Item - Icecrown Reputation Ring Healer Trigger +(74396, 84, 3, 0x28E212F7, 0x00119048, 0x00000000, 65536, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0), -- Fingers of Frost +(75455, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Chamber of Aspects 25 Melee Trinket +(75457, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 45000, 0), -- Item - Chamber of Aspects 25 Heroic Melee Trinket +(75465, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0), -- Item - Chamber of Aspects 25 Nuker Trinket +(75474, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x1, 0, 0x0, 0, 0, 45000, 0); -- Item - Chamber of Aspects 25 Heroic Nuker Trinket + +-- Spell script names for DK proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_dk_butchery', 'spell_dk_mark_of_blood', 'spell_dk_unholy_blight', 'spell_dk_vendetta', 'spell_dk_necrosis', 'spell_dk_runic_power_back_on_snare_root'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-48979, 'spell_dk_butchery'), -- Butchery (all ranks) +(49005, 'spell_dk_mark_of_blood'), -- Mark of Blood +(49194, 'spell_dk_unholy_blight'), -- Unholy Blight +(50154, 'spell_dk_vendetta'), -- Vendetta +(-51459, 'spell_dk_necrosis'), -- Necrosis (all ranks) +(61257, 'spell_dk_runic_power_back_on_snare_root'); -- Runic Power Back on Snare/Root + +-- Spell script names for Druid proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_dru_glyph_of_innervate', 'spell_dru_glyph_of_rake', 'spell_dru_leader_of_the_pack', 'spell_dru_glyph_of_rejuvenation', 'spell_dru_eclipse'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(54832, 'spell_dru_glyph_of_innervate'), -- Glyph of Innervate +(54821, 'spell_dru_glyph_of_rake'), -- Glyph of Rake +(24932, 'spell_dru_leader_of_the_pack'), -- Leader of the Pack +(54754, 'spell_dru_glyph_of_rejuvenation'), -- Glyph of Rejuvenation +(-48516, 'spell_dru_eclipse'); -- Eclipse (all ranks) + +-- Spell script names for Rogue proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_rog_glyph_of_backstab', 'spell_rog_master_of_subtlety', 'spell_rog_cut_to_the_chase', 'spell_rog_deadly_brew', 'spell_rog_quick_recovery'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(56800, 'spell_rog_glyph_of_backstab'), -- Glyph of Backstab +(31666, 'spell_rog_master_of_subtlety'), -- Master of Subtlety (periodic tracker) +(-35541, 'spell_rog_cut_to_the_chase'), -- Cut to the Chase (all ranks) +(-51625, 'spell_rog_deadly_brew'), -- Deadly Brew (all ranks) +(-31244, 'spell_rog_quick_recovery'); -- Quick Recovery (all ranks) + +-- Spell script names for Hunter proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_hun_thrill_of_the_hunt', 'spell_hun_hunting_party', 'spell_hun_rapid_recuperation', 'spell_hun_glyph_of_mend_pet'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-34497, 'spell_hun_thrill_of_the_hunt'), -- Thrill of the Hunt (all ranks) +(-53290, 'spell_hun_hunting_party'), -- Hunting Party (all ranks) +(53228, 'spell_hun_rapid_recuperation'), -- Rapid Recuperation Rank 1 +(53232, 'spell_hun_rapid_recuperation'), -- Rapid Recuperation Rank 2 +(57870, 'spell_hun_glyph_of_mend_pet'); -- Glyph of Mend Pet + +-- Spell script names for Paladin proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_pal_judgements_of_the_wise', 'spell_pal_righteous_vengeance', 'spell_pal_sheath_of_light', 'spell_pal_judgement_of_light_heal', 'spell_pal_judgement_of_wisdom_mana', 'spell_pal_spiritual_attunement', 'spell_pal_glyph_of_holy_light_proc'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-31876, 'spell_pal_judgements_of_the_wise'), -- Judgements of the Wise (all ranks) +(-53380, 'spell_pal_righteous_vengeance'), -- Righteous Vengeance (all ranks) +(-53501, 'spell_pal_sheath_of_light'), -- Sheath of Light (all ranks) +(20185, 'spell_pal_judgement_of_light_heal'), -- Judgement of Light (debuff) +(20186, 'spell_pal_judgement_of_wisdom_mana'), -- Judgement of Wisdom (debuff) +(31785, 'spell_pal_spiritual_attunement'), -- Spiritual Attunement Rank 1 +(33776, 'spell_pal_spiritual_attunement'), -- Spiritual Attunement Rank 2 +(54937, 'spell_pal_glyph_of_holy_light_proc'); -- Glyph of Holy Light + +-- Spell script names for Shaman proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_sha_glyph_of_healing_wave', 'spell_sha_spirit_hunt', 'spell_sha_frozen_power', 'spell_sha_lightning_overload', 'spell_sha_ancestral_awakening'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(55440, 'spell_sha_glyph_of_healing_wave'), -- Glyph of Healing Wave +(58877, 'spell_sha_spirit_hunt'), -- Spirit Hunt +(-63373, 'spell_sha_frozen_power'), -- Frozen Power (all ranks) +(-30675, 'spell_sha_lightning_overload'), -- Lightning Overload (all ranks) +(-51474, 'spell_sha_ancestral_awakening'); -- Ancestral Awakening (all ranks) + +-- Spell script names for Priest proc handlers +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_pri_vampiric_embrace', 'spell_pri_glyph_of_dispel_magic', 'spell_pri_body_and_soul', 'spell_pri_improved_shadowform'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(15286, 'spell_pri_vampiric_embrace'), -- Vampiric Embrace +(55677, 'spell_pri_glyph_of_dispel_magic'), -- Glyph of Dispel Magic +(-64127, 'spell_pri_body_and_soul'), -- Body and Soul (all ranks) +(47569, 'spell_pri_improved_shadowform'), -- Improved Shadowform Rank 1 +(47570, 'spell_pri_improved_shadowform'); -- Improved Shadowform Rank 2 + +-- Spell script names for Warlock proc handlers +-- Note: Nightfall uses -18094 with 'spell_warl_glyph_of_corruption_nightfall' (covers all ranks) +-- Glyph of Corruption (56218) also uses same script via RegisterSpellScriptWithArgs + +-- ============================================================================ +-- Phase 2: Migrate spell_proc_event entries with non-default values to spell_proc +-- These 68 entries have procFlags, ppmRate, customChance, or Cooldown set +-- ============================================================================ + +-- First delete any existing entries that might conflict +DELETE FROM `spell_proc` WHERE `SpellId` IN ( + -31641, -16689, 4524, 9452, 15257, 15331, 15332, 16372, 21747, + 24256, 26016, 27539, 27997, 28460, 29307, 31221, 31222, 31223, 33511, + 33522, 35399, 37565, 38319, 40303, 42760, 43730, 43983, 44546, 44548, + 44549, 44835, 45278, 45396, 45398, 45444, 46102, 49027, 49542, 49543, + 50871, 52881, 54404, 55610, 55717, 56845, 57351, 58426, 59887, 59888, + 59889, 59890, 59891, 60617, 62337, 64764, 64936, 66865, 66889, + 67530, 70871, 71567, 71604, 72256, 72673, 72674, 72675 +); + +-- Insert migrated entries from spell_proc_event +-- Schema: SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, +-- ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges +DELETE FROM `spell_proc` WHERE `SpellId` IN (-31641,-16689,4524,9452); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +-- Blazing Speed (Mage) - procFlags=680 (TAKEN_MELEE_AUTO_ATTACK|TAKEN_DAMAGE) +(-31641, 0, 0, 0x00000000, 0x00000000, 0x00000000, 680, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Shadow Weaving (Priest) - Cooldown=1000 +(-16689, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 1000, 0), +-- -16086 removed - TC doesn't have this entry, let DBC defaults handle it +-- Cure Ailments (Pet) - procFlags=1048576 (TAKEN_SPELL_MAGIC_DMG_CLASS) +(4524, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1048576, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Duelist's Riposte - ppmRate=3.0 +(9452, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 3, 0, 0, 0), +-- Shadow Weaving Rank 1 - procFlags=327680 (DONE_SPELL_MAGIC_DMG_CLASS), customChance=33 +(15257, 0, 0, 0x00000000, 0x00000000, 0x00000000, 327680, 0x0, 0x0, 0, 0x0, 0, 33, 0, 0), +-- Shadow Weaving Rank 2 - procFlags=327680, customChance=66 +(15331, 0, 0, 0x00000000, 0x00000000, 0x00000000, 327680, 0x0, 0x0, 0, 0x0, 0, 66, 0, 0), +-- Shadow Weaving Rank 3 - procFlags=327680, customChance=100 +(15332, 0, 0, 0x00000000, 0x00000000, 0x00000000, 327680, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Ancestral Fortitude (Shaman) - procFlags=131072, customChance=100 +(16372, 0, 0, 0x00000000, 0x00000000, 0x00000000, 131072, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Lawbringer - procFlags=20, ppmRate=20.0, Cooldown=50000 (AC #10402) +(21747, 0, 0, 0x00000000, 0x00000000, 0x00000000, 20, 0x0, 0x0, 0, 0x0,20, 0, 50000, 0), +-- Blessing of the Claw - Cooldown=240000 (4 min) +(24256, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0,240000, 0), +-- Thorn Shield - ppmRate=2.0 +(26016, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 2, 0, 0, 0), +-- Thick Chitin - Cooldown=10000 +(27539, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 10000, 0), +-- Bloodgorged - Cooldown=50000 +(27997, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 50000, 0), +-- Monstrous Vitality - Cooldown=5000 +(28460, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 5000, 0), +-- Arcane Power (Boss) - procFlags=4, customChance=100 +(29307, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- NOTE: Master of Subtlety spell_proc entries removed - TC uses script on 31666 (periodic tracker) instead of talent proc +-- If this causes regressions, uncomment these entries: +-- (31221, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1024, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Master of Subtlety Rank 1 +-- (31222, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1024, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Master of Subtlety Rank 2 +-- (31223, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1024, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), -- Master of Subtlety Rank 3 +-- Zandalarian Hero Charm - Cooldown=17000 +(33511, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 17000, 0), +-- Idol of the Raven Goddess - Cooldown=25000 +(33522, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 25000, 0), +-- Inspiration (Priest) - procFlags=131072 +(35399, 0, 0, 0x00000000, 0x00000000, 0x00000000, 131072, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Blessing of the Onyx Serpent - procFlags=16384 +(37565, 0, 0, 0x00000000, 0x00000000, 0x00000000, 16384, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Thundering Rage - Cooldown=50000 +(38319, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 50000, 0), +-- Ashtongue Talisman of Acumen - Cooldown=1000 +(40303, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 1000, 0), +-- Focused Assault - procFlags=245966, customChance=20 +(42760, 0, 0, 0x00000000, 0x00000000, 0x00000000, 245966, 0x0, 0x0, 0, 0x0, 0, 20, 0, 0), +-- Spirit Wolf - Cooldown=8000 +(43730, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 8000, 0), +-- Enchant Cloak - Steelweave - procFlags=81920, customChance=100, Cooldown=600 +(43983, 0, 0, 0x00000000, 0x00000000, 0x00000000, 81920, 0x0, 0x0, 0, 0x0, 0, 100, 600, 0), +-- Brain Freeze Rank 1 - procFlags=69632, customChance=5, Cooldown=3000 +(44546, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x0, 0, 0x0, 0, 5, 3000, 0), +-- Brain Freeze Rank 2 - procFlags=69632, customChance=10, Cooldown=3000 +(44548, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x0, 0, 0x0, 0, 10, 3000, 0), +-- Brain Freeze Rank 3 - procFlags=69632, customChance=15, Cooldown=3000 +(44549, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x0, 0, 0x0, 0, 15, 3000, 0), +-- Maim Interrupt - procFlags=16 +(44835, 0, 0, 0x00000000, 0x00000000, 0x00000000, 16, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Improved Water Shield - procFlags=82944 +(45278, 0, 0, 0x00000000, 0x00000000, 0x00000000, 82944, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Blessed Book of Nagrand - Cooldown=45000 +(45396, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0), +-- Memento of Tyrande - Cooldown=45000 +(45398, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0), +-- Tome of the Lightbringer - Cooldown=45000 +(45444, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 45000, 0), +-- Forked Lightning - procFlags=81920 +(46102, 0, 0, 0x00000000, 0x00000000, 0x00000000, 81920, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Wandering Plague Rank 1 - customChance=3, Cooldown=20000 +(49027, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 3, 20000, 0), +-- Wandering Plague Rank 2 - customChance=6, Cooldown=20000 +(49542, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 6, 20000, 0), +-- Wandering Plague Rank 3 - customChance=9, Cooldown=20000 +(49543, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 9, 20000, 0), +-- Divine Storm - customChance=100 +(50871, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Blade Warding - Cooldown=12000 +(52881, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 12000, 0), +-- Rapid Killing - customChance=100 +(54404, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Imp Devotion - procFlags=4096 +(55610, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4096, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Berserking - Cooldown=5000 +(55717, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 5000, 0), +-- Glyph of Scourge Strike - procFlags=2097152 +(56845, 0, 0, 0x00000000, 0x00000000, 0x00000000, 2097152, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Fel Vitality - procFlags=1782780 +(57351, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1782780, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Overkill - procFlags=1024 +(58426, 0, 0, 0x00000000, 0x00000000, 0x00000000, 1024, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Frostfire Orb Rank 1 - procFlags=87040 +(59887, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87040, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Frostfire Orb Rank 2 - procFlags=87040 +(59888, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87040, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Frostfire Orb Rank 3 - procFlags=87040 +(59889, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87040, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Frostfire Orb Rank 4 - procFlags=87040 +(59890, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87040, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Frostfire Orb Rank 5 - procFlags=87040 +(59891, 0, 0, 0x00000000, 0x00000000, 0x00000000, 87040, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Rage of Thassarian - customChance=100 +(60617, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Petrified Bark (Normal) - procFlags=40 +(62337, 0, 0, 0x00000000, 0x00000000, 0x00000000, 40, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- 62933 removed - TC doesn't have this entry, let DBC defaults handle it +-- Dying Curse - Cooldown=50000 +(64764, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 50000, 0), +-- Glyph of Scourge Strike - procFlags=69632, customChance=100 +(64936, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69632, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Talisman of the Forsaken City - customChance=35, Cooldown=3000 +(66865, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 35, 3000, 0), +-- Mana Shield - procFlags=4, customChance=100 +(66889, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Thorns - procFlags=40, Cooldown=5000 +(67530, 0, 0, 0x00000000, 0x00000000, 0x00000000, 40, 0x0, 0x0, 0, 0x0, 0, 0, 5000, 0), +-- Devious Minds - procFlags=69972, customChance=100 +(70871, 0, 0, 0x00000000, 0x00000000, 0x00000000, 69972, 0x0, 0x0, 0, 0x0, 0, 100, 0, 0), +-- Mutated Plague - Cooldown=250 +(71567, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 250, 0), +-- Deathbringer's Will Normal - customChance=100, Cooldown=10000 +(71604, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 10000, 0), +-- Crimson Scourge - procFlags=4 +(72256, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0), +-- Deathbringer's Will Heroic 1 - customChance=100, Cooldown=10000 +(72673, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 10000, 0), +-- Deathbringer's Will Heroic 2 - customChance=100, Cooldown=10000 +(72674, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 10000, 0), +-- Deathbringer's Will Heroic 3 - customChance=100, Cooldown=10000 +(72675, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 100, 10000, 0); + +-- Additional spell_proc entries from TrinityCore (missing in AzerothCore) +-- These entries provide refined proc configuration from TC's spell_proc table +DELETE FROM `spell_proc` WHERE `SpellId` IN (-59887,-53583,-51682,-49200,-47230,-31226,-30482,-16689,-14143,-12317,-11103,-7302,-7001,-1120,-588,-168,4341,5118,12043,12328,13159,13234,16166,17116,17670,18708,20911,21084,23591,32065,35321,36659,37604,38363,39215,40816,41350,45092,48504,50871,50908,53257,53515,53651,53817,57529,57531,58501,60617,63057,63849,70656,70904,71567,71571,71573,71865,71868,71993,72059,75490,75495); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +-- Negative SpellId entries (applies to all spell ranks) +(-59887, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0, 0), -- Frostfire Orb (all ranks) +(-53583, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0, 0), -- Swift Retribution (all ranks) +(-51682, 0, 8, 0x00000000, 0x00080000, 0x00000000, 0, 0x4, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Rogue Deadly Brew (all ranks) +(-49200,126, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Dispersion (all ranks) +(-47230, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Fel Synergy (all ranks) +(-31226, 0, 8, 0x00000000, 0x00080000, 0x00000000, 0, 0x5, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Rogue Deadly Poison (all ranks) +(-30482, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1027, 0x2, 0, 0, 0, 0, 0), -- Molten Shields (all ranks) +(-16689, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 1000, 0), -- Nature's Grasp (all ranks) +(-14143, 0, 8, 0x47046286, 0x00200000, 0x00000000, 0, 0x1, 0x2, 0, 0x8, 0, 0, 0, 0, 0), -- Rogue Relentless Strikes (all ranks) +(-12317, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Enrage (all ranks) +(-11103, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x0, 0, 0, 0, 0, 0), -- World in Flames (all ranks) +(-7302, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1027, 0x2, 0, 0, 0, 0, 0), -- Frost Armor (all ranks) +(-7001, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Lightwell Renew (all ranks) +(-1120, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x1, 3, 0, 0, 0, 0), -- Drain Soul (all ranks) +(-588, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Inner Fire (all ranks) +(-168, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1027, 0x2, 0, 0, 0, 0, 0), -- Frost Armor (all ranks) +-- Positive SpellId entries (specific spells) +(4341, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4096, 0x1, 0x1, 0, 0x0, 0, 0, 0, 0, 0), -- Flame Buffet +(5118, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Aspect of the Cheetah +(12043, 0, 3, 0x61400035, 0x00001000, 0x00000000, 0, 0x7, 0x1, 0, 0x8, 0, 0, 0, 0, 0), -- Presence of Mind +(12328, 0, 4, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Sweeping Strikes +(13159, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Aspect of the Pack +(13234, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1027, 0x2, 0, 0, 0, 0, 0), -- Magic Resistance +(16166, 0, 11, 0x00000003, 0x00001000, 0x00000000, 0, 0x7, 0x1, 0, 0x8, 0, 0, 0, 0, 0), -- Elemental Mastery +(17116, 0, 7, 0x10000861, 0x02000020, 0x00008000, 0, 0x7, 0x1, 0, 0x8, 0, 0, 0, 0, 0), -- Nature's Swiftness +(17670, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 1, 0, 0, 0, 0), -- Argent Dawn Commission +(18708, 0, 5, 0x20000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0, 0), -- Fel Domination +(20911, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 112, 0x0, 0, 0, 0, 0, 0), -- Blessing of Sanctuary +(21084, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Seal of Righteousness +(23591, 0, 10, 0x00800000, 0x00000000, 0x00000000, 16, 0x0, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Reckoning +(32065, 0, 0, 0x00000000, 0x00000000, 0x00000000, 524288, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Fungal Decay +(35321, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Gust of Wind +(36659, 0, 0, 0x00000000, 0x00000000, 0x00000000, 524288, 0x1, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Tail Lash +(37604, 0, 6, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0, 0), -- Primal Blessing +(38363, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Gust of Wind +(39215, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Gust of Wind +(40816, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x2, 0, 0x0, 0, 0, 0, 7000, 0), -- Saber Lash +(41350, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Aura of Desire +(45092, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 0, 0, 0, 0, 0), -- Faction, Spar Buddy +(48504, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Living Seed +(50871, 0, 9, 0x00000000, 0x40000000, 0x00000000, 0, 0x1, 0x2, 2, 0x0, 0, 0, 0, 0, 0), -- Brutal Gladiator's War Edge +(50908, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 2, 0, 0, 4000, 0), -- Serpent-Coil Braid +(53257, 0, 9, 0x00000000, 0x10000000, 0x00000000, 16, 0x1, 0x2, 2, 0x8, 0, 0, 0, 0, 0), -- Cobra Strikes +(53515, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x3, 0x2, 0, 0x0, 0, 0, 0, 0, 0), -- Divine Aegis +(53651, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x0, 0, 0x2, 0, 0, 0, 0, 0), -- Beacon of Light +(53817, 0, 11, 0x000001C3, 0x00008000, 0x00000000, 0, 0x0, 0x1, 0, 0x8, 0, 0, 0, 0, 0), -- Maelstrom Weapon +(57529, 0, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0, 0), -- Arcane Potency (Rank 1 trigger) +(57531, 0, 3, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0, 0), -- Arcane Potency (Rank 2 trigger) +(58501, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 4, 0, 0, 30000, 0), -- Soul Fire (TC) +(63057, 0, 7, 0x00000000, 0x00040000, 0x00000000, 16384, 0x0, 0x2, 0, 0x0, 0, 0, 0, 0, 0), -- Nourish +(63849, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x0, 0, 0x0, 2, 0, 0, 0, 0), -- Hand of Salvation +(70656, 0, 15, 0x00000000, 0x00000000, 0x00000000, 0, 0x0, 0x1, 0, 0x0, 0, 0, 0, 0, 0), -- Blood Strike (trigger) +(70904, 0, 6, 0x00000000, 0x00000000, 0x00000800, 2048, 0x4, 0x0, 0, 0x0, 0, 0, 0, 1000, 0), -- Shadow Word: Pain (trigger) +(71571, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Gutripper (Normal) +(71573, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Gutripper (Heroic) +(71865, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Unbound Plague +(71868, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Unbound Plague (Heroic) +(71993, 0, 0, 0x00000000, 0x00000000, 0x00000000, 4, 0x0, 0x0, 12287, 0x0, 0, 0, 0, 3000, 0), -- Deathwhisper Necromancer +(72059, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x1, 0x0, 1027, 0x2, 0, 0, 0, 0, 0), -- Frost Damage Immunity +(75490, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x2, 0, 0, 0, 0, 0), -- Frostfire Orb +(75495, 0, 0, 0x00000000, 0x00000000, 0x00000000, 0, 0x2, 0x2, 0, 0x2, 0, 0, 0, 0, 0); -- Frostfire Orb + +-- Sync spell_proc values from TrinityCore +-- SpellFamilyName, SpellFamilyMask, SchoolMask differences +UPDATE `spell_proc` SET `SpellFamilyName`=3, `SpellFamilyMask0`=0, `SpellFamilyMask1`=34, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=-31571; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=-29441; +UPDATE `spell_proc` SET `SpellFamilyName`=8, `SpellFamilyMask0`=1107296782, `SpellFamilyMask1`=2, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=-14186; +UPDATE `spell_proc` SET `SpellFamilyName`=8, `SpellFamilyMask0`=1191182854, `SpellFamilyMask1`=2097152, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=-14143; +UPDATE `spell_proc` SET `SpellFamilyName`=6, `SpellFamilyMask0`=41984016, `SpellFamilyMask1`=9218, `SpellFamilyMask2`=8, `SchoolMask`=32 WHERE `SpellId`=15286; +UPDATE `spell_proc` SET `SpellFamilyName`=7, `SpellFamilyMask0`=268436065, `SpellFamilyMask1`=33554464, `SpellFamilyMask2`=32768, `SchoolMask`=0 WHERE `SpellId`=17116; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=2416967683, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=26119; +UPDATE `spell_proc` SET `SpellFamilyName`=3, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=44401; +UPDATE `spell_proc` SET `SpellFamilyName`=5, `SpellFamilyMask0`=357, `SpellFamilyMask1`=131264, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=54274; +UPDATE `spell_proc` SET `SpellFamilyName`=5, `SpellFamilyMask0`=357, `SpellFamilyMask1`=131264, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=54276; +UPDATE `spell_proc` SET `SpellFamilyName`=5, `SpellFamilyMask0`=357, `SpellFamilyMask1`=131264, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=54277; +UPDATE `spell_proc` SET `SpellFamilyName`=3, `SpellFamilyMask0`=0, `SpellFamilyMask1`=16384, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=56374; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=4 WHERE `SpellId`=71756; +UPDATE `spell_proc` SET `SpellFamilyName`=3, `SpellFamilyMask0`=0, `SpellFamilyMask1`=1048576, `SpellFamilyMask2`=0, `SchoolMask`=0 WHERE `SpellId`=71761; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=4 WHERE `SpellId`=72782; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=4 WHERE `SpellId`=72783; +UPDATE `spell_proc` SET `SpellFamilyName`=0, `SpellFamilyMask0`=0, `SpellFamilyMask1`=0, `SpellFamilyMask2`=0, `SchoolMask`=4 WHERE `SpellId`=72784; + +-- SpellTypeMask, SpellPhaseMask, AttributesMask, Cooldown, HitMask, ProcFlags, Chance sync from TrinityCore +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=100, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-63730; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=64, `Chance`=0 WHERE `SpellId`=-61846; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=8259, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-58872; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=16, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-57878; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=5800, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-56636; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=2, `Cooldown`=22000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-56342; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=100, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-54639; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-53569; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-53486; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-53380; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-53290; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=2, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-52127; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-51940; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-51634; +UPDATE `spell_proc` SET `SpellTypeMask`=5, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-51625; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-51521; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-50880; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-49217; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=100, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-49208; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-49182; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=20000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-49027; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-48988; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=-48539; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-47516; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-47509; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=-47245; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-47195; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-46867; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-46854; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-45234; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-44557; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-44449; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-41635; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-34950; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=8000, `HitMask`=1027, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-34935; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=1, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-34753; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-34500; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-33881; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=6000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-33150; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-33142; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-31656; +UPDATE `spell_proc` SET `SpellTypeMask`=7, `SpellPhaseMask`=4, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=16384, `Chance`=0 WHERE `SpellId`=-31571; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=664232, `Chance`=100 WHERE `SpellId`=-30701; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=500, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-30160; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-29723; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-29074; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-20049; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=-19572; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-19184; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-18094; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-16958; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-16257; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-16256; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-14892; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=500, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-14186; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=64, `Chance`=0 WHERE `SpellId`=-13165; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-12319; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-11213; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=3, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-11180; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-10400; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=2, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-974; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=2, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=-324; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=8, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=1719; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=256, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=7383; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=7434; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=12, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=12536; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=20000, `HitMask`=16, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=13163; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=5000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=15088; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=15286; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=128, `Cooldown`=2000, `HitMask`=0, `ProcFlags`=0, `Chance`=2 WHERE `SpellId`=15600; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=500, `HitMask`=2, `ProcFlags`=65536, `Chance`=0 WHERE `SpellId`=16164; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=12, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=16246; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=16620; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=12, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=16870; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=17364; +UPDATE `spell_proc` SET `SpellTypeMask`=7, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=34816, `Chance`=0 WHERE `SpellId`=17619; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=8, `Cooldown`=0, `HitMask`=0, `ProcFlags`=65536, `Chance`=0 WHERE `SpellId`=17941; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=20164; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=20165; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=20166; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=20375; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=20784; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=1000, `HitMask`=64, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=22618; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=120000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=22648; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23552; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=1000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23572; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23578; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23581; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23686; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=23689; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=24353; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=25669; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=16, `Chance`=0 WHERE `SpellId`=26135; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0, `ProcsPerMinute`=10 WHERE `SpellId`=26480; -- `AC` #16777 +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=27656; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=27774; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=28716; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=28752; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=28845; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29150; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29501; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29624; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29625; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29626; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29632; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29633; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29634; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29635; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29636; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=29637; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0, `ProcsPerMinute`=18 WHERE `SpellId`=30823; -- `AC` #17499 +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=32734; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=35000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=32837; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=32844; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=33297; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=25000, `HitMask`=0, `ProcFlags`=340, `Chance`=15 WHERE `SpellId`=33510; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=33648; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=279552, `Chance`=0 WHERE `SpellId`=33953; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34074; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34320; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34355; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34584; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34586; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34598; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=20000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34774; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=4000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=34827; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=35077; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=35080; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=35083; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=35086; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=35121; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=36111; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37170; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37173; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37213; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=40000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37247; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37381; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37600; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37603; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37655; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=2500, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=37657; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=256, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38026; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=120000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38164; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38290; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38299; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38334; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38350; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=38394; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=39027; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=1, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=39442; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=39443; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=40000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=39958; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=40438; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=40475; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=40478; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=40482; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=8000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=40899; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=32, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=41393; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=41434; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=41989; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=42083; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=90000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=42135; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=90000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=42136; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=10000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=43748; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=43750; +UPDATE `spell_proc` SET `SpellTypeMask`=5, `SpellPhaseMask`=1, `AttributesMask`=8, `Cooldown`=0, `HitMask`=0, `ProcFlags`=69632, `Chance`=0 WHERE `SpellId`=44401; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=7 WHERE `SpellId`=44543; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=15 WHERE `SpellId`=44545; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45054; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45057; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45354; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45355; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45481; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45482; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=45483; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=16384, `Chance`=0 WHERE `SpellId`=45484; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=46569; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=25000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=46662; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=46832; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=46916; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=10000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=48833; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=10000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=48837; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=2000, `HitMask`=0, `ProcFlags`=139944, `Chance`=0 WHERE `SpellId`=49222; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=8528552, `Chance`=0 WHERE `SpellId`=49592; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=49622; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=8, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=49796; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=4, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=50240; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=4, `AttributesMask`=8, `Cooldown`=0, `HitMask`=0, `ProcFlags`=65552, `Chance`=0 WHERE `SpellId`=51124; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=4, `AttributesMask`=0, `Cooldown`=1000, `HitMask`=2, `ProcFlags`=0, `Chance`=33 WHERE `SpellId`=51698; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=4, `AttributesMask`=0, `Cooldown`=1000, `HitMask`=2, `ProcFlags`=0, `Chance`=66 WHERE `SpellId`=51700; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=4, `AttributesMask`=0, `Cooldown`=1000, `HitMask`=2, `ProcFlags`=0, `Chance`=100 WHERE `SpellId`=51701; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=10000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=52020; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=52420; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=32, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=52423; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=16, `Chance`=0 WHERE `SpellId`=52437; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=52898; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=53397; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=5000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=53646; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54278; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54646; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54695; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54707; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54754; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54808; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=2500, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54841; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=54909; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=40000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55380; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55440; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55640; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55689; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55747; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=55000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=55776; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=56218; +UPDATE `spell_proc` SET `SpellTypeMask`=4, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=2049, `ProcFlags`=65536, `Chance`=0 WHERE `SpellId`=56375; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3500, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=56451; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=500, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=56821; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=57345; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=57351; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=57870; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=57907; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=58442; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=5000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=58444; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=58901; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=59176; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=59345; +UPDATE `spell_proc` SET `SpellTypeMask`=5, `SpellPhaseMask`=1, `AttributesMask`=2, `Cooldown`=35000, `HitMask`=0, `ProcFlags`=69648, `Chance`=0 WHERE `SpellId`=59630; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=294912, `Chance`=0 WHERE `SpellId`=60061; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60066; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60170; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=262144, `Chance`=0 WHERE `SpellId`=60176; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60221; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60301; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60306; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60317; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60436; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60442; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60473; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60487; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60519; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60529; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60770; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=60826; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=61188; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=61356; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=61618; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=62114; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=62115; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=63251; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=63280; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=279552, `Chance`=0 WHERE `SpellId`=64411; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=279552, `Chance`=0 WHERE `SpellId`=64415; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=32, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64440; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64738; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64752; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64786; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64792; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64824; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64860; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64867; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64890; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=64914; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=65002; +UPDATE `spell_proc` SET `SpellTypeMask`=3, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=81920, `Chance`=0 WHERE `SpellId`=65007; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=65013; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=65020; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=65025; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67151; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=15000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67209; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67353; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=5000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67356; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=6000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67361; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67363; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=8000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67365; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=9000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67379; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=9000, `HitMask`=0, `ProcFlags`=16, `Chance`=0 WHERE `SpellId`=67392; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67653; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=8388948, `Chance`=0 WHERE `SpellId`=67672; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67702; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=2000, `HitMask`=2, `ProcFlags`=69632, `Chance`=0 WHERE `SpellId`=67712; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=2000, `HitMask`=2, `ProcFlags`=69632, `Chance`=0 WHERE `SpellId`=67758; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=67771; +UPDATE `spell_proc` SET `SpellTypeMask`=7, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=69739; +UPDATE `spell_proc` SET `SpellTypeMask`=7, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=69755; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=256, `Cooldown`=100, `HitMask`=0, `ProcFlags`=87040, `Chance`=0 WHERE `SpellId`=69762; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3000, `HitMask`=16, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70188; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70664; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=12287, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70672; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70727; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70730; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=4, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70803; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70811; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70841; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=70854; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71174; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71176; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71178; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71198; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71402; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=2, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71404; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=50 WHERE `SpellId`=71406; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71540; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=50 WHERE `SpellId`=71545; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71585; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=65536, `Chance`=0 WHERE `SpellId`=71602; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=100000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71606; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71634; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=100000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71637; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=30000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71640; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=65536, `Chance`=0 WHERE `SpellId`=71645; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71756; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=3000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71770; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=71903; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=10 WHERE `SpellId`=72413; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=327680, `Chance`=0 WHERE `SpellId`=72417; +UPDATE `spell_proc` SET `SpellTypeMask`=2, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=60000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72419; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=12287, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72455; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72782; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72783; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=2, `Cooldown`=0, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72784; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=12287, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72832; +UPDATE `spell_proc` SET `SpellTypeMask`=0, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=0, `HitMask`=12287, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=72833; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75455; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=2, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75457; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75465; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=1, `AttributesMask`=0, `Cooldown`=50000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75474; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75475; +UPDATE `spell_proc` SET `SpellTypeMask`=1, `SpellPhaseMask`=0, `AttributesMask`=0, `Cooldown`=45000, `HitMask`=0, `ProcFlags`=0, `Chance`=0 WHERE `SpellId`=75481; + +-- Missing TC spell_proc entries +DELETE FROM `spell_proc` WHERE `SpellId` IN (60617, 71567); +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(60617, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0), -- Unknown (proc on absorb) +(71567, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 250, 0); -- ICC buff (heal proc) + +-- Fix duplicate entries (remove positive when negative already covers all ranks) +DELETE FROM `spell_proc` WHERE `SpellId` IN (9452, 44546, 49027, 59887); + +-- Delete AC-specific entries that don't exist in TC's spell_proc (they cause warnings and aren't needed) +DELETE FROM `spell_proc` WHERE `SpellId` IN (-16086, 62933); + +-- Delete AC-specific legacy entries that don't exist in TC's spell_proc +-- These have invalid ProcFlags/SpellPhaseMask combinations and cause warnings +DELETE FROM `spell_proc` WHERE `SpellId` IN ( + 16086, -- Piercing Howl (rank handling) + 15257, 15331, 15332, -- Priest Shadow Weaving (handled by AuraScript) + 21747, -- Raptorslayer + 24256, -- Judgement of Crusader + 27997, -- Spell Vulnerability + 28460, -- Mojo Madness + 31221, 31222, 31223, -- Master of Subtlety + 33511, 33522, -- Concussion/Daze + 37565, -- Magma Shield + 38319, -- Mojo Madness + 40303, -- Thrash + 42760, -- Dark Transformation + 43730, -- Stormchops + 43983, -- Fiery Payback + 44835, -- Maim Interrupt + 45278, -- Chaos Bolt + 45396, 45398, -- Blackout + 45444, -- Totem of Ancestral Guidance + 46102, -- Living Flame + 54404, -- Demonic Immolation + 55610, -- Improved Icy Talons + 55717, -- Venom + 56845, -- Glyph of Seal of Command + 58426, -- Overkill + 59888, 59889, 59890, 59891, -- Borrowed Time ranks + 64936, -- Flame Leviathan Residue + 70871 -- Essence of the Blood Queen +); + +-- Fix additional duplicate entries +DELETE FROM `spell_proc` WHERE `SpellId` IN (26016, 44548, 44549, 49542, 49543); + +-- Fix spell_script_names bindings for proc system +-- Remove 44401 from spell_mage_gen_extra_effects (TC uses separate handler) +DELETE FROM `spell_script_names` WHERE `spell_id` = 44401 AND `ScriptName` = 'spell_mage_gen_extra_effects'; + +-- Fix Savage Defense binding (script is for 62606 absorb buff, not 62600 passive) +DELETE FROM `spell_script_names` WHERE `spell_id` = 62600 AND `ScriptName` = 'spell_dru_savage_defense'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (62606, 'spell_dru_savage_defense'); + +-- Fix Ancestral Awakening binding (was incorrectly bound to -51474 Astral Shift, should be -51556 Ancestral Awakening) +DELETE FROM `spell_script_names` WHERE `spell_id` = -51474 AND `ScriptName` = 'spell_sha_ancestral_awakening'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (-51556, 'spell_sha_ancestral_awakening'); + +-- Remove obsolete reload spell_proc_event command (replaced by spell_proc) +DELETE FROM `command` WHERE `name` = 'reload spell_proc_event'; + +-- Fingers of Frost - register script for charge consumption buff (74396) +DELETE FROM `spell_script_names` WHERE `spell_id` = 74396 AND `ScriptName` = 'spell_mage_fingers_of_frost'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(74396, 'spell_mage_fingers_of_frost'); + +-- Remove non-existent Fingers of Frost script entries +-- spell_mage_fingers_of_frost_proc and spell_mage_fingers_of_frost_proc_aura don't exist in code +-- The proc system now handles charge consumption via PrepareProcToTrigger +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_mage_fingers_of_frost_proc'; +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_mage_fingers_of_frost_proc_aura'; + +-- Sheath of Light: procs on critical heals (from TrinityCore) +-- ProcFlags=0 uses DBC flags, SpellTypeMask=2 (HEAL), SpellPhaseMask=2 (HIT), HitMask=2 (CRITICAL) +DELETE FROM `spell_proc` WHERE `SpellId` = -53501; +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(-53501, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0); + +-- Fix Cut to the Chase spell_script_names (was incorrectly registered to Combat Potency -35541) +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_rog_cut_to_the_chase'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (-51664, 'spell_rog_cut_to_the_chase'); + +-- Fix Decimation DisableEffectsMask (was 0, should be 2 to disable EFFECT_1 from proccing) +UPDATE `spell_proc` SET `DisableEffectsMask` = 2 WHERE `SpellId` = -63156; + +-- Vendetta: add missing spell_script_names entry (from TrinityCore) +DELETE FROM `spell_script_names` WHERE `spell_id` = -49015 AND `ScriptName` = 'spell_dk_vendetta'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (-49015, 'spell_dk_vendetta'); + +-- Remove duplicate spell_dk_runic_power_back_on_snare_root (TC only has spell_dk_pvp_4p_bonus for 61257) +DELETE FROM `spell_script_names` WHERE `spell_id` = 61257 AND `ScriptName` = 'spell_dk_runic_power_back_on_snare_root'; + +-- Lightning Shield: add missing spell_script_names entry +DELETE FROM `spell_script_names` WHERE `spell_id` = -324 AND `ScriptName` = 'spell_sha_lightning_shield'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (-324, 'spell_sha_lightning_shield'); + +-- Nightfall (Glyph of Corruption): add missing spell_script_names entry +DELETE FROM `spell_script_names` WHERE `spell_id` = 56218 AND `ScriptName` = 'spell_warl_nightfall'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (56218, 'spell_warl_nightfall'); + +-- Drop obsolete spell_proc_event table (replaced by spell_proc) +DROP TABLE IF EXISTS `spell_proc_event`; + +-- Darkmoon Card: Greatness - Register script to handle proc based on highest stat +DELETE FROM `spell_script_names` WHERE `spell_id` = 57345; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(57345, 'spell_item_darkmoon_card_greatness'); + +-- Death's Choice / Death's Verdict - Register script to handle proc based on highest stat (Str vs Agi) +DELETE FROM `spell_script_names` WHERE `spell_id` IN (67702, 67771); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(67702, 'spell_item_death_choice'), +(67771, 'spell_item_death_choice'); + +-- Soul Preserver - Register script to handle proc based on class +DELETE FROM `spell_script_names` WHERE `spell_id` = 60510; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(60510, 'spell_item_soul_preserver'); + +-- Amani Charm of the Witch Doctor - Register script to handle heal proc +DELETE FROM `spell_script_names` WHERE `spell_id` = 43820; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(43820, 'spell_item_charm_witch_doctor'); + +-- Lifegiving Gem - Gift of Life spell +DELETE FROM `spell_script_names` WHERE `spell_id` = 23725; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(23725, 'spell_item_lifegiving_gem'); + +-- Mana Drain - proc script for mana drain items +DELETE FROM `spell_script_names` WHERE `spell_id` IN (27522, 40336); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(27522, 'spell_item_mana_drain'), +(40336, 'spell_item_mana_drain'); + +-- Gnomish Mind Control Cap +DELETE FROM `spell_script_names` WHERE `spell_id` = 13180; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(13180, 'spell_item_mind_control_cap'); + +-- Hourglass Sand +DELETE FROM `spell_script_names` WHERE `spell_id` = 30536; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(30536, 'spell_item_hourglass_sand'); + +-- Ultrasafe Transporter: Toshley's Station +DELETE FROM `spell_script_names` WHERE `spell_id` = 36941; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(36941, 'spell_item_ultrasafe_transporter'); + +-- Power Circle (Mage T5 Set Bonus) +DELETE FROM `spell_script_names` WHERE `spell_id` = 45043; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(45043, 'spell_item_power_circle'); + +-- Thrallmar's Favor / Honor Hold's Favor +DELETE FROM `spell_script_names` WHERE `spell_id` IN (32096, 32098); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(32096, 'spell_item_thrallmar_and_honor_hold_favor'), +(32098, 'spell_item_thrallmar_and_honor_hold_favor'); + +-- Drums of Forgotten Kings +DELETE FROM `spell_script_names` WHERE `spell_id` = 69378; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(69378, 'spell_item_drums_of_forgotten_kings'); + +-- Drums of the Wild +DELETE FROM `spell_script_names` WHERE `spell_id` = 69381; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(69381, 'spell_item_drums_of_the_wild'); + +-- Darkmoon Card: Illusion +DELETE FROM `spell_script_names` WHERE `spell_id` = 57350; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(57350, 'spell_item_darkmoon_card_illusion'); + +-- Mad Alchemist's Potion +DELETE FROM `spell_script_names` WHERE `spell_id` = 28374; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(28374, 'spell_item_mad_alchemists_potion'); + +-- Decahedral Dwarven Dice +DELETE FROM `spell_script_names` WHERE `spell_id` = 47770; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(47770, 'spell_item_decahedral_dwarven_dice'); + +-- Arena Drink - modifies drink mana regen behavior in arenas +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_gen_arena_drink'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(430, 'spell_gen_arena_drink'), +(431, 'spell_gen_arena_drink'), +(432, 'spell_gen_arena_drink'), +(1133, 'spell_gen_arena_drink'), +(1135, 'spell_gen_arena_drink'), +(1137, 'spell_gen_arena_drink'), +(10250, 'spell_gen_arena_drink'), +(22734, 'spell_gen_arena_drink'), +(27089, 'spell_gen_arena_drink'), +(34291, 'spell_gen_arena_drink'), +(43182, 'spell_gen_arena_drink'), +(43183, 'spell_gen_arena_drink'), +(46755, 'spell_gen_arena_drink'), +(49472, 'spell_gen_arena_drink'), +(57073, 'spell_gen_arena_drink'), +(61830, 'spell_gen_arena_drink'), +(72623, 'spell_gen_arena_drink'); + +-- Remove scripts with DBC mismatches (spell effects don't match AC's DBC) +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ( +'spell_item_nitro_boosts', +'spell_item_nitro_boosts_backfire', +'spell_item_harm_prevention_belt', +'spell_item_dire_brew', +'spell_item_dimensional_ripper_everlook', +'spell_item_red_rider_air_rifle', +'spell_item_runic_healing_injector', +'spell_item_taunt_flag_targeting', +'spell_item_extract_gas', +'spell_item_disco_ball_listening_to_music_periodic', +'spell_item_disco_ball_listening_to_music_check', +'spell_item_disco_ball_listening_to_music_parent' +); diff --git a/data/sql/updates/pending_db_world/rev_1769292856469402686.sql b/data/sql/updates/pending_db_world/rev_1769292856469402686.sql new file mode 100644 index 000000000..ad6cd3321 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769292856469402686.sql @@ -0,0 +1,105 @@ +-- Lock and Load - Add spell_proc entry with correct SpellPhaseMask for periodic damage procs +-- SpellPhaseMask changed from 4 (PROC_SPELL_PHASE_FINISH) to 2 (PROC_SPELL_PHASE_HIT) +-- This allows Black Arrow, Explosive Trap, and Immolation Trap periodic damage to trigger Lock and Load +DELETE FROM `spell_proc` WHERE `SpellId` = -56342; +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(-56342, 0, 9, 24, 134217728, 147456, 0, 0, 2, 0, 2, 0, 0, 0, 22000, 0); + +-- Killing Machine - register spell script +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_dk_killing_machine'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(51124, 'spell_dk_killing_machine'); + +-- Elemental Focus - register spell script to prevent weapon imbue attacks from proccing Clearcasting +DELETE FROM `spell_script_names` WHERE `spell_id` = 16164 AND `ScriptName` = 'spell_sha_elemental_focus'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(16164, 'spell_sha_elemental_focus'); + +-- Light's Beacon (53651) - Beacon of Light heal transfer script +DELETE FROM `spell_script_names` WHERE `spell_id` = 53651; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(53651, 'spell_pal_light_s_beacon'); + +-- Mage spell scripts from TrinityCore proc system port +DELETE FROM `spell_script_names` WHERE `spell_id` IN (-5143, -31661, -44614, 45438, 44401); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-5143, 'spell_mage_arcane_missiles'), +(-31661, 'spell_mage_dragon_breath'), +(-44614, 'spell_mage_frostfire_bolt'), +(45438, 'spell_mage_ice_block'), +(44401, 'spell_mage_missile_barrage_proc'); + +-- Paladin scripts refactored from hardcoded SpellAuras.cpp and Spell.cpp to proper scripts +-- Aura Mastery (31821) - Applies/removes Aura Mastery Immune aura +DELETE FROM `spell_script_names` WHERE `spell_id` = 31821; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(31821, 'spell_pal_aura_mastery'); + +-- Aura Mastery Immune (64364) - Area target check to filter immunity to only Concentration Aura targets +DELETE FROM `spell_script_names` WHERE `spell_id` = 64364; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(64364, 'spell_pal_aura_mastery_immune'); + +-- Beacon of Light (53563) - Periodic tick handler to ensure correct caster GUID propagation +DELETE FROM `spell_script_names` WHERE `spell_id` = 53563 AND `ScriptName` = 'spell_pal_beacon_of_light'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(53563, 'spell_pal_beacon_of_light'); + +-- Sacred Shield (58597) - Absorb amount calculation with ICC buff support +DELETE FROM `spell_script_names` WHERE `spell_id` = 58597 AND `ScriptName` = 'spell_pal_sacred_shield'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(58597, 'spell_pal_sacred_shield'); + +-- Divine Protection (498), Divine Shield (642), Hand of Protection (-1022) +-- Applies Forbearance, Avenging Wrath marker, and Immune Shield marker +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_pal_immunities'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(498, 'spell_pal_immunities'), +(642, 'spell_pal_immunities'), +(-1022, 'spell_pal_immunities'); + +-- Improved Concentration Aura (-20254), Improved Devotion Aura (-20138) +-- Sanctified Retribution (31869), Swift Retribution (-53379) +-- Handles applying/removing the improved aura buff effects +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_pal_improved_concentraction_aura', 'spell_pal_improved_devotion_aura', 'spell_pal_sanctified_retribution', 'spell_pal_swift_retribution'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-20254, 'spell_pal_improved_concentraction_aura'), +(-20138, 'spell_pal_improved_devotion_aura'), +(31869, 'spell_pal_sanctified_retribution'), +(-53379, 'spell_pal_swift_retribution'); + +-- Warrior scripts - Vigilance redirect threat and Warrior's Wrath (T2 5P) +DELETE FROM `spell_script_names` WHERE `spell_id` IN (59665, 21977); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(59665, 'spell_warr_vigilance_redirect_threat'), +(21977, 'spell_warr_warriors_wrath'); + +-- Druid Forms Trinket (37336) - SpellPhaseMask required for proc system +-- ProcFlags=0 uses DBC flags (0x15414), Chance=0 uses DBC chance (3%) +DELETE FROM `spell_proc` WHERE `SpellId` = 37336; +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(37336, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0); + +-- Living Root of the Wildheart (37336) - Item trinket script +DELETE FROM `spell_script_names` WHERE `spell_id` = 37336; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(37336, 'spell_item_living_root_of_the_wildheart'); + +-- Druid scripts ported from TrinityCore +-- Frenzied Regeneration (22842) - Converts rage to health +-- Nourish (50464) - Glyph of Nourish support +-- Insect Swarm (-5570) - T8 Balance Relic support +-- T9 Feral Relic (67353) - Idol of Mutilation form-specific procs +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_dru_frenzied_regeneration', 'spell_dru_nourish', 'spell_dru_insect_swarm', 'spell_dru_t9_feral_relic'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(22842, 'spell_dru_frenzied_regeneration'), +(50464, 'spell_dru_nourish'), +(-5570, 'spell_dru_insect_swarm'), +(67353, 'spell_dru_t9_feral_relic'); + +-- Priest scripts ported from TrinityCore +-- Pain and Suffering (-47580) - Prevents EFFECT_1 DUMMY from proccing +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_pri_pain_and_suffering_dummy'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(47580, 'spell_pri_pain_and_suffering_dummy'), +(47581, 'spell_pri_pain_and_suffering_dummy'); diff --git a/data/sql/updates/pending_db_world/rev_1769396722032747978.sql b/data/sql/updates/pending_db_world/rev_1769396722032747978.sql new file mode 100644 index 000000000..608335533 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769396722032747978.sql @@ -0,0 +1,18 @@ +-- +DELETE FROM `spell_script_names` WHERE `spell_id` IN (52179, 16246, 16191, -30881, 55278, 55328, 55329, 55330, 55332, 55333, 55335, 58589, 58590, 58591, 28820); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(52179, 'spell_sha_astral_shift_visual_dummy'), +(16246, 'spell_sha_clearcasting'), +(16191, 'spell_sha_mana_tide'), +(-30881, 'spell_sha_nature_guardian'), +(55278, 'spell_sha_stoneclaw_totem'), +(55328, 'spell_sha_stoneclaw_totem'), +(55329, 'spell_sha_stoneclaw_totem'), +(55330, 'spell_sha_stoneclaw_totem'), +(55332, 'spell_sha_stoneclaw_totem'), +(55333, 'spell_sha_stoneclaw_totem'), +(55335, 'spell_sha_stoneclaw_totem'), +(58589, 'spell_sha_stoneclaw_totem'), +(58590, 'spell_sha_stoneclaw_totem'), +(58591, 'spell_sha_stoneclaw_totem'), +(28820, 'spell_sha_t3_8p_bonus'); diff --git a/data/sql/updates/pending_db_world/rev_1769728867586741519.sql b/data/sql/updates/pending_db_world/rev_1769728867586741519.sql new file mode 100644 index 000000000..5e420fa88 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769728867586741519.sql @@ -0,0 +1,31 @@ +-- Register Nether Protection spell script +DELETE FROM `spell_script_names` WHERE `spell_id` = -30299; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-30299, 'spell_warl_nether_protection'); + +-- Register Curse of Agony spell script +DELETE FROM `spell_script_names` WHERE `spell_id` = -980; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-980, 'spell_warl_curse_of_agony'); + +-- Fix Nightfall and Glyph of Corruption registrations +DELETE FROM `spell_script_names` WHERE `spell_id` = -18094; +DELETE FROM `spell_script_names` WHERE `spell_id` = 56218; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-18094, 'spell_warl_nightfall'), +(56218, 'spell_warl_glyph_of_corruption_nightfall'); + +-- Register Acclimation spell script (DK) +DELETE FROM `spell_script_names` WHERE `spell_id` = -49200; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(-49200, 'spell_dk_acclimation'); + +-- Register Advantage T10 4P spell script (DK) +DELETE FROM `spell_script_names` WHERE `spell_id` = 70656; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(70656, 'spell_dk_advantage_t10_4p'); + +-- Register Glyph of Barkskin spell script (Druid) +DELETE FROM `spell_script_names` WHERE `spell_id` = 63057; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(63057, 'spell_dru_glyph_of_barkskin'); diff --git a/data/sql/updates/pending_db_world/rev_1769815350392767493.sql b/data/sql/updates/pending_db_world/rev_1769815350392767493.sql new file mode 100644 index 000000000..0674f3d60 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769815350392767493.sql @@ -0,0 +1,4 @@ +-- Hunter T9 4P Bonus - spell script registration +DELETE FROM `spell_script_names` WHERE `spell_id` = 67151; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(67151, 'spell_hun_t9_4p_bonus'); diff --git a/data/sql/updates/pending_db_world/rev_1769974041727457907.sql b/data/sql/updates/pending_db_world/rev_1769974041727457907.sql new file mode 100644 index 000000000..830ecbf62 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769974041727457907.sql @@ -0,0 +1,2 @@ +-- Omen of Clarity should only proc from damage and healing spells, not utility spells like Furor +UPDATE `spell_proc` SET `SpellTypeMask` = 3 WHERE `SpellId` = 16864; diff --git a/data/sql/updates/pending_db_world/rev_1769983684458633094.sql b/data/sql/updates/pending_db_world/rev_1769983684458633094.sql new file mode 100644 index 000000000..75c212cd0 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1769983684458633094.sql @@ -0,0 +1,8 @@ +-- +-- The Lightning Capacitor, Thunder Capacitor, Reign of the Dead/Unliving trinkets +DELETE FROM `spell_script_names` WHERE `spell_id` IN (37657, 54841, 67712, 67758); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(37657, 'spell_item_lightning_capacitor'), +(54841, 'spell_item_thunder_capacitor'), +(67712, 'spell_item_toc25_caster_trinket_normal'), +(67758, 'spell_item_toc25_caster_trinket_heroic'); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 47d15362a..a27181eec 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7372,7 +7372,7 @@ void Player::CastItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 if (entry) { if (entry->PPMChance) - chance = GetPPMProcChance(proto->Delay, entry->PPMChance, spellInfo); + chance = GetPPMProcChance(GetAttackTime(attType), entry->PPMChance, spellInfo); else if (entry->customChance) chance = (float)entry->customChance; } @@ -9745,8 +9745,8 @@ bool Player::IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod if (!mod || !spellInfo) return false; - // Mod out of charges - if (spell && mod->charges == -1 && spell->m_appliedMods.find(mod->ownerAura) == spell->m_appliedMods.end()) + // First time this aura applies a mod to us and is out of charges + if (spell && mod->ownerAura && mod->ownerAura->IsUsingCharges() && !mod->ownerAura->GetCharges() && !spell->m_appliedMods.count(mod->ownerAura)) return false; // +duration to infinite duration spells making them limited @@ -9769,7 +9769,7 @@ void Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell* s auto calculateSpellMod = [&](SpellModifier* mod) { // xinef: temporary pets cannot use charged mods of owner, needed for mirror image QQ they should use their own auras - if (temporaryPet && mod->charges != 0) + if (temporaryPet && mod->ownerAura && mod->ownerAura->IsUsingCharges()) return; // skip if already instant or cost is free @@ -9801,10 +9801,10 @@ void Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell* s if (mod->op == SPELLMOD_CASTING_TIME && basevalue >= T(10000) && mod->value <= -100) return; // xinef: special exception for surge of light, dont affect crit chance if previous mods were not applied - else if (mod->op == SPELLMOD_CRITICAL_CHANCE && spell && !HasSpellMod(mod, spell)) + else if (mod->op == SPELLMOD_CRITICAL_CHANCE && !HasSpellModApplied(mod, spell)) return; // xinef: special case for backdraft gcd reduce with backlast time reduction, dont affect gcd if cast time was not applied - else if (mod->op == SPELLMOD_GLOBAL_COOLDOWN && spell && !HasSpellMod(mod, spell)) + else if (mod->op == SPELLMOD_GLOBAL_COOLDOWN && !HasSpellModApplied(mod, spell)) return; // xinef: those two mods should be multiplicative (Glyph of Renew) @@ -9814,7 +9814,7 @@ void Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell* s totalmul += CalculatePct(1.0f, mod->value); } - DropModCharge(mod, spell); + ApplyModToSpell(mod, spell); }; // Drop charges for triggering spells instead of triggered ones @@ -9823,10 +9823,6 @@ void Player::ApplySpellMod(uint32 spellId, SpellModOp op, T& basevalue, Spell* s for (auto mod : m_spellMods[op]) { - // Charges can be set only for mods with auras - if (!mod->ownerAura) - ASSERT(!mod->charges); - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; @@ -9843,17 +9839,6 @@ template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, i template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, uint32& basevalue, Spell* spell, bool temporaryPet); template AC_GAME_API void Player::ApplySpellMod(uint32 spellId, SpellModOp op, float& basevalue, Spell* spell, bool temporaryPet); -// Binary predicate for sorting SpellModifiers -struct SpellModPredicate -{ - bool operator() (SpellModifier const* a, SpellModifier const* b) const - { - if (a->type != b->type) - return a->type == SPELLMOD_FLAT; - return a->priority > b->priority; - } -}; - void Player::AddSpellMod(SpellModifier* mod, bool apply) { LOG_DEBUG("spells.aura", "Player::AddSpellMod {}", mod->spellId); @@ -9870,7 +9855,7 @@ void Player::AddSpellMod(SpellModifier* mod, bool apply) if (mod->mask & _mask) { int32 val = 0; - for (SpellModList::iterator itr = m_spellMods[mod->op].begin(); itr != m_spellMods[mod->op].end(); ++itr) + for (SpellModContainer::iterator itr = m_spellMods[mod->op].begin(); itr != m_spellMods[mod->op].end(); ++itr) { if ((*itr)->type == mod->type && (*itr)->mask & _mask) val += (*itr)->value; @@ -9886,12 +9871,11 @@ void Player::AddSpellMod(SpellModifier* mod, bool apply) if (apply) { - m_spellMods[mod->op].push_back(mod); - m_spellMods[mod->op].sort(SpellModPredicate()); + m_spellMods[mod->op].insert(mod); } else { - m_spellMods[mod->op].remove(mod); + m_spellMods[mod->op].erase(mod); // mods bound to aura will be removed in AuraEffect::~AuraEffect if (!mod->ownerAura) delete mod; @@ -9908,7 +9892,7 @@ void Player::RestoreSpellMods(Spell* spell, uint32 ownerAuraId, Aura* aura) for (uint8 i = 0; i < MAX_SPELLMOD; ++i) { - for (SpellModList::iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end(); ++itr) + for (SpellModContainer::iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end(); ++itr) { SpellModifier* mod = *itr; @@ -9937,20 +9921,6 @@ void Player::RestoreSpellMods(Spell* spell, uint32 ownerAuraId, Aura* aura) // from applied mods (Else, an aura with two mods on the current spell would // only see the first of its modifier restored) aurasQueue.push_back(mod->ownerAura); - - // add mod charges back to mod - if (mod->charges == -1) - mod->charges = 1; - else - mod->charges++; - - // Do not set more spellmods than available - if (mod->ownerAura->GetCharges() < mod->charges) - mod->charges = mod->ownerAura->GetCharges(); - - // Skip this check for now - aura charges may change due to various reason - /// @todo track these changes correctly - //ASSERT (mod->ownerAura->GetCharges() <= mod->charges); } } @@ -9985,14 +9955,14 @@ void Player::RemoveSpellMods(Spell* spell) for (uint8 i = 0; i < MAX_SPELLMOD; ++i) { - for (SpellModList::const_iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end();) + for (SpellModContainer::const_iterator itr = m_spellMods[i].begin(); itr != m_spellMods[i].end();) { SpellModifier* mod = *itr; ++itr; - // don't handle spells with proc_event entry defined + // don't handle spells with spell_proc entry defined // this is a temporary workaround, because all spellmods should be handled like that - if (sSpellMgr->GetSpellProcEvent(mod->spellId)) + if (sSpellMgr->GetSpellProcEntry(mod->spellId)) { continue; } @@ -10018,20 +9988,14 @@ void Player::RemoveSpellMods(Spell* spell) if (sp->SpellIconID == 3261 || sp->SpellIconID == 2999 || sp->SpellIconID == 2938) if (AuraEffect* aurEff = GetAuraEffectDummy(64869)) if (roll_chance_i(aurEff->GetAmount())) - { - mod->charges = 1; - continue; - } + continue; // don't consume charge } // ROGUE MUTILATE WITH COLD BLOOD if (spellInfo->Id == 5374) { SpellInfo const* sp = mod->ownerAura->GetSpellInfo(); if (sp->Id == 14177) // Cold Blood - { - mod->charges = 1; - continue; - } + continue; // don't consume charge } if (mod->ownerAura->DropCharge(AURA_REMOVE_BY_EXPIRE)) @@ -10040,15 +10004,25 @@ void Player::RemoveSpellMods(Spell* spell) } } -void Player::DropModCharge(SpellModifier* mod, Spell* spell) +void Player::ApplyModToSpell(SpellModifier* mod, Spell* spell) { - if (spell && mod->ownerAura && mod->charges > 0) - { - if (--mod->charges == 0) - mod->charges = -1; + if (!spell) + return; - spell->m_appliedMods.insert(mod->ownerAura); - } + // don't do anything with no charges + if (mod->ownerAura->IsUsingCharges() && !mod->ownerAura->GetCharges()) + return; + + // register inside spell, proc system uses this to drop charges + spell->m_appliedMods.insert(mod->ownerAura); +} + +bool Player::HasSpellModApplied(SpellModifier* mod, Spell* spell) +{ + if (!spell) + return false; + + return spell->m_appliedMods.count(mod->ownerAura) != 0; } void Player::SetSpellModTakingSpell(Spell* spell, bool apply) @@ -11770,6 +11744,9 @@ void Player::ApplyEquipCooldown(Item* pItem) if (pItem->GetTemplate()->HasFlag(ITEM_FLAG_NO_EQUIP_COOLDOWN)) return; + if (GetCommandStatus(CHEAT_COOLDOWN)) + return; + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { _Spell const& spellData = pItem->GetTemplate()->Spells[i]; @@ -11778,11 +11755,15 @@ void Player::ApplyEquipCooldown(Item* pItem) if (!spellData.SpellId) continue; - // xinef: apply hidden cooldown for procs + // apply proc cooldown to equip auras if we have any if (spellData.SpellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP) { - // xinef: uint32(-1) special marker for proc cooldowns - AddSpellCooldown(spellData.SpellId, uint32(-1), 30 * IN_MILLISECONDS); + SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(spellData.SpellId); + if (!procEntry) + continue; + + if (Aura* itemAura = GetAura(spellData.SpellId, GetGUID(), pItem->GetGUID())) + itemAura->AddProcCooldown(std::chrono::steady_clock::now() + std::chrono::seconds(30)); continue; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index d6cb920ee..32c191702 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -180,20 +180,19 @@ enum TalentTree // talent tabs // Spell modifier (used for modify other spells) struct SpellModifier { - SpellModifier(Aura* _ownerAura = nullptr) : op(SPELLMOD_DAMAGE), type(SPELLMOD_FLAT), charges(0), mask(), ownerAura(_ownerAura) {} - SpellModOp op : 8; - SpellModType type : 8; - int16 charges : 16; - int32 value{0}; + SpellModifier(Aura* _ownerAura = nullptr) : op(SPELLMOD_DAMAGE), type(SPELLMOD_FLAT), value(0), mask(), spellId(0), ownerAura(_ownerAura) {} + + SpellModOp op; + SpellModType type; + int32 value; flag96 mask; - uint32 spellId{0}; + uint32 spellId; Aura* const ownerAura; - uint32 priority{0}; }; typedef std::unordered_map PlayerTalentMap; typedef std::unordered_map PlayerSpellMap; -typedef std::list SpellModList; +typedef std::unordered_set SpellModContainer; typedef GuidList WhisperListContainer; @@ -1801,7 +1800,8 @@ public: void RemoveSpellMods(Spell* spell); void RestoreSpellMods(Spell* spell, uint32 ownerAuraId = 0, Aura* aura = nullptr); void RestoreAllSpellMods(uint32 ownerAuraId = 0, Aura* aura = nullptr); - void DropModCharge(SpellModifier* mod, Spell* spell); + static void ApplyModToSpell(SpellModifier* mod, Spell* spell); + [[nodiscard]] static bool HasSpellModApplied(SpellModifier* mod, Spell* spell); void SetSpellModTakingSpell(Spell* spell, bool apply); [[nodiscard]] bool HasSpellCooldown(uint32 spell_id) const override; @@ -2640,7 +2640,7 @@ public: // mt maps [[nodiscard]] const PlayerTalentMap& GetTalentMap() const { return m_talents; } [[nodiscard]] uint32 GetNextSave() const { return m_nextSave; } - [[nodiscard]] SpellModList const& GetSpellModList(uint32 type) const { return m_spellMods[type]; } + [[nodiscard]] SpellModContainer const& GetSpellModList(uint32 type) const { return m_spellMods[type]; } void SetServerSideVisibility(ServerSideVisibilityType type, AccountTypes sec); void SetServerSideVisibilityDetect(ServerSideVisibilityType type, AccountTypes sec); @@ -2873,7 +2873,7 @@ protected: uint32 m_baseHealthRegen; int32 m_spellPenetrationItemMod; - SpellModList m_spellMods[MAX_SPELLMOD]; + SpellModContainer m_spellMods[MAX_SPELLMOD]; //uint32 m_pad; // Spell* m_spellModTakingSpell; // Spell for which charges are dropped in spell::finish diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5352f8b40..9f63466b1 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -99,20 +99,9 @@ float playerBaseMoveSpeed[MAX_MOVE_TYPE] = 3.14f // MOVE_PITCH_RATE }; -// Used for prepare can/can`t triggr aura -static bool InitTriggerAuraData(); -// Define can trigger auras -static bool isTriggerAura[TOTAL_AURAS]; -// Define can't trigger auras (need for disable second trigger) -static bool isNonTriggerAura[TOTAL_AURAS]; -// Triggered always, even from triggered spells -static bool isAlwaysTriggeredAura[TOTAL_AURAS]; -// Prepare lists -static bool procPrepared = InitTriggerAuraData(); - DamageInfo::DamageInfo(Unit* _attacker, Unit* _victim, uint32 _damage, SpellInfo const* _spellInfo, SpellSchoolMask _schoolMask, DamageEffectType _damageType, uint32 cleanDamage) : m_attacker(_attacker), m_victim(_victim), m_damage(_damage), m_spellInfo(_spellInfo), m_schoolMask(_schoolMask), - m_damageType(_damageType), m_attackType(BASE_ATTACK), m_cleanDamage(cleanDamage) + m_damageType(_damageType), m_attackType(BASE_ATTACK), m_cleanDamage(cleanDamage), m_hitMask(0) { m_absorb = 0; m_resist = 0; @@ -126,23 +115,123 @@ DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo) : DamageInfo(DamageInfo(dm DamageInfo::DamageInfo(DamageInfo const& dmg1, DamageInfo const& dmg2) : m_attacker(dmg1.m_attacker), m_victim(dmg1.m_victim), m_damage(dmg1.m_damage + dmg2.m_damage), m_spellInfo(dmg1.m_spellInfo), m_schoolMask(SpellSchoolMask(dmg1.m_schoolMask | dmg2.m_schoolMask)), m_damageType(dmg1.m_damageType), m_attackType(dmg1.m_attackType), m_absorb(dmg1.m_absorb + dmg2.m_absorb), m_resist(dmg1.m_resist + dmg2.m_resist), m_block(dmg1.m_block), - m_cleanDamage(dmg1.m_cleanDamage + dmg1.m_cleanDamage) + m_cleanDamage(dmg1.m_cleanDamage + dmg1.m_cleanDamage), m_hitMask(dmg1.m_hitMask | dmg2.m_hitMask) { } DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex) : m_attacker(dmgInfo.attacker), m_victim(dmgInfo.target), m_damage(dmgInfo.damages[damageIndex].damage), m_spellInfo(nullptr), m_schoolMask(SpellSchoolMask(dmgInfo.damages[damageIndex].damageSchoolMask)), m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType), m_absorb(dmgInfo.damages[damageIndex].absorb), m_resist(dmgInfo.damages[damageIndex].resist), m_block(dmgInfo.blocked_amount), - m_cleanDamage(dmgInfo.cleanDamage) + m_cleanDamage(dmgInfo.cleanDamage), m_hitMask(0) { + switch (dmgInfo.hitOutCome) + { + case MELEE_HIT_MISS: + m_hitMask |= PROC_HIT_MISS; + break; + case MELEE_HIT_DODGE: + m_hitMask |= PROC_HIT_DODGE; + break; + case MELEE_HIT_PARRY: + m_hitMask |= PROC_HIT_PARRY; + break; + case MELEE_HIT_EVADE: + m_hitMask |= PROC_HIT_EVADE; + break; + case MELEE_HIT_BLOCK: + m_hitMask |= PROC_HIT_BLOCK; + [[fallthrough]]; + case MELEE_HIT_CRUSHING: + case MELEE_HIT_GLANCING: + case MELEE_HIT_NORMAL: + m_hitMask |= PROC_HIT_NORMAL; + break; + case MELEE_HIT_CRIT: + m_hitMask |= PROC_HIT_CRITICAL; + break; + default: + break; + } + + if (dmgInfo.damages[damageIndex].absorb) + m_hitMask |= PROC_HIT_ABSORB; + + if (dmgInfo.blocked_amount) + m_hitMask |= (dmgInfo.damages[damageIndex].damage == dmgInfo.blocked_amount) ? PROC_HIT_FULL_BLOCK : PROC_HIT_BLOCK; } -DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType) +DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, uint32 hitMask) : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), m_spellInfo(spellNonMeleeDamage.spellInfo), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), - m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), - m_cleanDamage(spellNonMeleeDamage.cleanDamage) + m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), + m_cleanDamage(spellNonMeleeDamage.cleanDamage), m_hitMask(hitMask) { + if (spellNonMeleeDamage.blocked) + m_hitMask |= PROC_HIT_BLOCK; + if (spellNonMeleeDamage.absorb) + m_hitMask |= PROC_HIT_ABSORB; +} + +DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, SpellMissInfo missInfo) + : m_attacker(spellNonMeleeDamage.attacker), m_victim(spellNonMeleeDamage.target), m_damage(spellNonMeleeDamage.damage), + m_spellInfo(spellNonMeleeDamage.spellInfo), m_schoolMask(SpellSchoolMask(spellNonMeleeDamage.schoolMask)), m_damageType(damageType), + m_attackType(attackType), m_absorb(spellNonMeleeDamage.absorb), m_resist(spellNonMeleeDamage.resist), m_block(spellNonMeleeDamage.blocked), + m_cleanDamage(spellNonMeleeDamage.cleanDamage), m_hitMask(PROC_HIT_NONE) +{ + // Compute hitMask from SpellMissInfo + if (missInfo != SPELL_MISS_NONE) + { + switch (missInfo) + { + case SPELL_MISS_MISS: + m_hitMask |= PROC_HIT_MISS; + break; + case SPELL_MISS_RESIST: + m_hitMask |= PROC_HIT_FULL_RESIST; + break; + case SPELL_MISS_DODGE: + m_hitMask |= PROC_HIT_DODGE; + break; + case SPELL_MISS_PARRY: + m_hitMask |= PROC_HIT_PARRY; + break; + case SPELL_MISS_BLOCK: + m_hitMask |= PROC_HIT_BLOCK; + break; + case SPELL_MISS_EVADE: + m_hitMask |= PROC_HIT_EVADE; + break; + case SPELL_MISS_IMMUNE: + case SPELL_MISS_IMMUNE2: + m_hitMask |= PROC_HIT_IMMUNE; + break; + case SPELL_MISS_DEFLECT: + m_hitMask |= PROC_HIT_DEFLECT; + break; + case SPELL_MISS_ABSORB: + m_hitMask |= PROC_HIT_ABSORB; + break; + case SPELL_MISS_REFLECT: + m_hitMask |= PROC_HIT_REFLECT; + break; + default: + break; + } + } + else + { + // On block + if (spellNonMeleeDamage.blocked) + m_hitMask |= PROC_HIT_BLOCK; + // On absorb + if (spellNonMeleeDamage.absorb) + m_hitMask |= PROC_HIT_ABSORB; + // On crit + if (spellNonMeleeDamage.HitInfo & SPELL_HIT_TYPE_CRIT) + m_hitMask |= PROC_HIT_CRITICAL; + else + m_hitMask |= PROC_HIT_NORMAL; + } } void DamageInfo::ModifyDamage(int32 amount) @@ -172,6 +261,11 @@ void DamageInfo::BlockDamage(uint32 amount) m_damage -= amount; } +uint32 DamageInfo::GetHitMask() const +{ + return m_hitMask; +} + uint32 DamageInfo::GetUnmitigatedDamage() const { return m_damage + m_cleanDamage + m_absorb + m_resist; @@ -198,6 +292,20 @@ SpellInfo const* ProcEventInfo::GetSpellInfo() const return nullptr; } +SpellSchoolMask ProcEventInfo::GetSchoolMask() const +{ + if (_spell) + return _spell->GetSpellInfo()->GetSchoolMask(); + + if (_damageInfo) + return _damageInfo->GetSchoolMask(); + + if (_healInfo) + return _healInfo->GetSchoolMask(); + + return SPELL_SCHOOL_MASK_NONE; +} + // we can disable this warning for this since it only // causes undefined behavior when passed to the base class constructor #ifdef _MSC_VER @@ -1459,7 +1567,7 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama // Calculate absorb resist if (damageInfo->damage > 0) { - DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE); + DamageInfo dmgInfo(*damageInfo, SPELL_DIRECT_DAMAGE, BASE_ATTACK, 0); Unit::CalcAbsorbResist(dmgInfo); damageInfo->absorb = dmgInfo.GetAbsorb(); damageInfo->resist = dmgInfo.GetResist(); @@ -1514,7 +1622,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon damageInfo->HitInfo = 0; damageInfo->procAttacker = PROC_FLAG_NONE; damageInfo->procVictim = PROC_FLAG_NONE; - damageInfo->procEx = PROC_EX_NONE; damageInfo->hitOutCome = MELEE_HIT_EVADE; if (!victim) @@ -1561,8 +1668,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon { damageInfo->HitInfo |= HITINFO_NORMALSWING; damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE; - - damageInfo->procEx |= PROC_EX_IMMUNE; return; } } @@ -1623,7 +1728,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon case MELEE_HIT_EVADE: damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND; damageInfo->TargetState = VICTIMSTATE_EVADES; - damageInfo->procEx |= PROC_EX_EVADE; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { @@ -1635,7 +1739,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon case MELEE_HIT_MISS: damageInfo->HitInfo |= HITINFO_MISS; damageInfo->TargetState = VICTIMSTATE_INTACT; - damageInfo->procEx |= PROC_EX_MISS; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { @@ -1645,14 +1748,11 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon break; case MELEE_HIT_NORMAL: damageInfo->TargetState = VICTIMSTATE_HIT; - damageInfo->procEx |= PROC_EX_NORMAL_HIT; break; case MELEE_HIT_CRIT: { damageInfo->HitInfo |= HITINFO_CRITICALHIT; damageInfo->TargetState = VICTIMSTATE_HIT; - - damageInfo->procEx |= PROC_EX_CRITICAL_HIT; // Crit bonus calc for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) { @@ -1685,7 +1785,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon } case MELEE_HIT_PARRY: damageInfo->TargetState = VICTIMSTATE_PARRY; - damageInfo->procEx |= PROC_EX_PARRY; damageInfo->cleanDamage = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) @@ -1696,7 +1795,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon break; case MELEE_HIT_DODGE: damageInfo->TargetState = VICTIMSTATE_DODGE; - damageInfo->procEx |= PROC_EX_DODGE; damageInfo->cleanDamage = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) @@ -1709,7 +1807,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon { damageInfo->TargetState = VICTIMSTATE_HIT; damageInfo->HitInfo |= HITINFO_BLOCK; - damageInfo->procEx |= PROC_EX_BLOCK; damageInfo->blocked_amount = damageInfo->target->GetShieldBlockValue(); // double blocked amount if block is critical if (damageInfo->target->isBlockCritical()) @@ -1739,7 +1836,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon if (fullBlockMask == ((1 << 0) | (1 << 1))) { damageInfo->TargetState = VICTIMSTATE_BLOCKS; - damageInfo->procEx |= PROC_EX_FULL_BLOCK; damageInfo->blocked_amount -= remainingBlock; } break; @@ -1748,7 +1844,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon { damageInfo->HitInfo |= HITINFO_GLANCING; damageInfo->TargetState = VICTIMSTATE_HIT; - damageInfo->procEx |= PROC_EX_NORMAL_HIT; int32 leveldif = int32(victim->GetLevel()) - int32(GetLevel()); if (leveldif > 3) leveldif = 3; @@ -1765,7 +1860,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon case MELEE_HIT_CRUSHING: damageInfo->HitInfo |= HITINFO_CRUSHING; damageInfo->TargetState = VICTIMSTATE_HIT; - damageInfo->procEx |= PROC_EX_NORMAL_HIT; // 150% normal damage for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) @@ -1847,15 +1941,6 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon damageInfo->HitInfo |= (tmpHitInfo[0] & HITINFO_PARTIAL_RESIST); } - if (damageInfo->HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB)) - { - damageInfo->procEx |= PROC_EX_ABSORB; - } - - if (damageInfo->HitInfo & HITINFO_FULL_RESIST) - { - damageInfo->procEx |= PROC_EX_RESIST; - } } void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) @@ -1972,7 +2057,10 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) } if (IsPlayer()) - ToPlayer()->CastItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, damageInfo->procEx); + { + DamageInfo dmgInfo(*damageInfo); + ToPlayer()->CastItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, dmgInfo.GetHitMask()); + } // Do effect if any damage done to target if (damageInfo->damages[0].damage + damageInfo->damages[1].damage) @@ -2425,11 +2513,12 @@ void Unit::CalcAbsorbResist(DamageInfo& dmgInfo, bool Splited) uint32 splitted_absorb = 0; uint32 splitted_resist = 0; - uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT; + uint32 procAttacker = 0, procVictim = 0; DamageInfo splittedDmgInfo(attacker, caster, splitted, spellInfo, schoolMask, dmgInfo.GetDamageType()); + splittedDmgInfo.AddHitMask(PROC_HIT_NORMAL); if (caster->IsImmunedToDamageOrSchool(schoolMask)) { - procEx |= PROC_EX_IMMUNE; + splittedDmgInfo.AddHitMask(PROC_HIT_IMMUNE); splittedDmgInfo.AbsorbDamage(splitted); } else @@ -2442,9 +2531,13 @@ void Unit::CalcAbsorbResist(DamageInfo& dmgInfo, bool Splited) splitted_resist = splittedDmgInfo.GetResist(); splitted = splittedDmgInfo.GetDamage(); + // Add absorb to hitMask if damage was absorbed + if (splittedDmgInfo.GetAbsorb()) + splittedDmgInfo.AddHitMask(PROC_HIT_ABSORB); + // create procs createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim); - caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted, nullptr, -1, nullptr, &splittedDmgInfo); + caster->ProcSkillsAndReactives(true, attacker, procVictim, splittedDmgInfo.GetHitMask(), BASE_ATTACK, spellInfo, splitted, nullptr, -1, nullptr, &splittedDmgInfo); if (attacker) { @@ -2499,11 +2592,12 @@ void Unit::CalcAbsorbResist(DamageInfo& dmgInfo, bool Splited) uint32 splitted_absorb = 0; uint32 splitted_resist = 0; - uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT; + uint32 procAttacker = 0, procVictim = 0; DamageInfo splittedDmgInfo(attacker, caster, splitted, spellInfo, splitSchoolMask, dmgInfo.GetDamageType()); + splittedDmgInfo.AddHitMask(PROC_HIT_NORMAL); if (caster->IsImmunedToDamageOrSchool(schoolMask)) { - procEx |= PROC_EX_IMMUNE; + splittedDmgInfo.AddHitMask(PROC_HIT_IMMUNE); splittedDmgInfo.AbsorbDamage(splitted); } else @@ -2516,9 +2610,13 @@ void Unit::CalcAbsorbResist(DamageInfo& dmgInfo, bool Splited) splitted_resist = splittedDmgInfo.GetResist(); splitted = splittedDmgInfo.GetDamage(); + // Add absorb to hitMask if damage was absorbed + if (splittedDmgInfo.GetAbsorb()) + splittedDmgInfo.AddHitMask(PROC_HIT_ABSORB); + // create procs createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim); - caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted); + caster->ProcSkillsAndReactives(true, attacker, procVictim, splittedDmgInfo.GetHitMask(), BASE_ATTACK, spellInfo, splitted); if (attacker) { @@ -2673,14 +2771,12 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A SendAttackStateUpdate(&damageInfo); - //TriggerAurasProcOnEvent(damageInfo); - _lastDamagedTargetGuid = victim->GetGUID(); DealMeleeDamage(&damageInfo, true); DamageInfo dmgInfo(damageInfo); - Unit::ProcDamageAndSpell(damageInfo.attacker, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, damageInfo.procEx, dmgInfo.GetDamage(), + Unit::ProcSkillsAndAuras(damageInfo.attacker, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, dmgInfo.GetHitMask(), dmgInfo.GetDamage(), damageInfo.attackType, nullptr, nullptr, -1, nullptr, &dmgInfo); if (IsPlayer()) @@ -3535,7 +3631,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca if (reflectchance > 0 && roll_chance_i(reflectchance)) { // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) - //ProcDamageAndSpell(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); + //ProcSkillsAndAuras(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); return SPELL_MISS_REFLECT; } } @@ -3610,7 +3706,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanRef if (reflectchance > 0 && roll_chance_i(reflectchance)) { // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) - //ProcDamageAndSpell(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); + //ProcSkillsAndAuras(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell); return SPELL_MISS_REFLECT; } } @@ -4495,7 +4591,10 @@ Aura* Unit::_TryStackingOrRefreshingExistingAura(SpellInfo const* newAura, uint8 castItemGUID = castItem->GetGUID(); // find current aura from spell and change it's stackamount, or refresh it's duration - if (Aura* foundAura = GetOwnedAura(newAura->Id, newAura->HasAttribute(SPELL_ATTR0_CU_SINGLE_AURA_STACK) ? ObjectGuid::Empty : casterGUID, newAura->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC) ? castItemGUID : ObjectGuid::Empty, 0)) + // Use castItemGUID for passive auras (weapon imbues) and enchant procs so they can stack from dual-wield + bool useItemGuid = newAura->IsPassive() || newAura->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC); + ObjectGuid itemGuidForLookup = (useItemGuid && castItemGUID) ? castItemGUID : ObjectGuid::Empty; + if (Aura* foundAura = GetOwnedAura(newAura->Id, newAura->HasAttribute(SPELL_ATTR0_CU_SINGLE_AURA_STACK) ? ObjectGuid::Empty : casterGUID, itemGuidForLookup, 0)) { // effect masks do not match // extremely rare case @@ -6608,15 +6707,38 @@ void Unit::SendSpellNonMeleeDamageLog(Unit* target, SpellInfo const* spellInfo, SendSpellNonMeleeDamageLog(&log); } -void Unit::ProcDamageAndSpell(Unit* actor, Unit* victim, uint32 procAttacker, uint32 procVictim, uint32 procExtra, uint32 amount, WeaponAttackType attType, SpellInfo const* procSpellInfo, SpellInfo const* procAura, int8 procAuraEffectIndex, Spell const* procSpell, DamageInfo* damageInfo, HealInfo* healInfo, uint32 procPhase) +void Unit::ProcSkillsAndAuras(Unit* actor, Unit* victim, uint32 procAttacker, uint32 procVictim, uint32 procExtra, uint32 amount, WeaponAttackType attType, SpellInfo const* procSpellInfo, SpellInfo const* procAura, int8 procAuraEffectIndex, Spell const* procSpell, DamageInfo* damageInfo, HealInfo* healInfo, uint32 procPhase) { - // Not much to do if no flags are set. + // Handle skills and reactives for actor if (procAttacker && actor) - actor->ProcDamageAndSpellFor(false, victim, procAttacker, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); - // Now go on with a victim's events'n'auras - // Not much to do if no flags are set or there is no victim + actor->ProcSkillsAndReactives(false, victim, procAttacker, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); + + // Handle skills and reactives for victim if (victim && victim->IsAlive() && procVictim) - victim->ProcDamageAndSpellFor(true, actor, procVictim, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); + victim->ProcSkillsAndReactives(true, actor, procVictim, procExtra, attType, procSpellInfo, amount, procAura, procAuraEffectIndex, procSpell, damageInfo, healInfo, procPhase); + + // Handle aura procs via new proc system (TriggerAurasProcOnEvent) + if (actor) + { + // Calculate spellTypeMask based on phase and actual damage/heal info + uint32 spellTypeMask = 0; + if (procPhase == PROC_SPELL_PHASE_CAST || procPhase == PROC_SPELL_PHASE_FINISH) + { + // At CAST phase, no damage/heal has occurred yet - use MASK_ALL to allow + // procs that check for damage/heal type based on spell info (like Backlash) + // At FINISH phase, damageInfo may be null but spell did do damage - use MASK_ALL + // to match TrinityCore behavior (see TC Spell.cpp PROC_SPELL_PHASE_FINISH call) + spellTypeMask = PROC_SPELL_TYPE_MASK_ALL; + } + else if (healInfo && healInfo->GetHeal()) + spellTypeMask = PROC_SPELL_TYPE_HEAL; + else if (damageInfo && damageInfo->GetDamage()) + spellTypeMask = PROC_SPELL_TYPE_DAMAGE; + else if (procSpellInfo) + spellTypeMask = PROC_SPELL_TYPE_NO_DMG_HEAL; + + actor->TriggerAurasProcOnEvent(nullptr, nullptr, victim, procAttacker, procVictim, spellTypeMask, procPhase, procExtra, const_cast(procSpell), damageInfo, healInfo); + } } void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* pInfo) @@ -6818,3349 +6940,6 @@ void Unit::SendAttackStateUpdate(uint32 HitInfo, Unit* target, uint8 /*SwingType SendAttackStateUpdate(&dmgInfo); } -//victim may be nullptr -bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, ProcEventInfo const& eventInfo) -{ - SpellInfo const* dummySpell = triggeredByAura->GetSpellInfo(); - uint32 effIndex = triggeredByAura->GetEffIndex(); - int32 triggerAmount = triggeredByAura->GetAmount(); - Spell const* spellProc = eventInfo.GetProcSpell(); - - Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() - ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; - - uint32 triggered_spell_id = 0; - uint32 cooldown_spell_id = 0; // for random trigger, will be one of the triggered spell to avoid repeatable triggers - // otherwise, it's the triggered_spell_id by default - Unit* target = victim; - int32 basepoints0 = 0; - ObjectGuid originalCaster; - - switch (dummySpell->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - { - switch (dummySpell->Id) - { - // Overkill - case 58426: - { - triggered_spell_id = 58427; - break; - } - // Unstable Power - case 24658: - { - if (!procSpell || procSpell->Id == 24659) - return false; - // Need remove one 24659 aura - RemoveAuraFromStack(24659); - return true; - } - // Restless Strength - case 24661: - { - // Need remove one 24662 aura - RemoveAuraFromStack(24662); - return true; - } - // Mark of Malice - case 33493: - { - if (triggeredByAura->GetBase()->GetCharges() > 1) - return true; - - target = this; - triggered_spell_id = 33494; - break; - } - // Twisted Reflection (boss spell) - case 21063: - triggered_spell_id = 21064; - break; - // Vampiric Aura (boss spell) - case 38196: - { - basepoints0 = 3 * damage; // 300% - if (basepoints0 < 0) - return false; - - triggered_spell_id = 31285; - target = this; - break; - } - // Aura of Madness (Darkmoon Card: Madness trinket) - //===================================================== - // 39511 Sociopath: +35 strength (Paladin, Rogue, Druid, Warrior) - // 40997 Delusional: +70 attack power (Rogue, Hunter, Paladin, Warrior, Druid) - // 40998 Kleptomania: +35 agility (Warrior, Rogue, Paladin, Hunter, Druid) - // 40999 Megalomania: +41 damage/healing (Druid, Shaman, Priest, Warlock, Mage, Paladin) - // 41002 Paranoia: +35 spell/melee/ranged crit strike rating (All classes) - // 41005 Manic: +35 haste (spell, melee and ranged) (All classes) - // 41009 Narcissism: +35 intellect (Druid, Shaman, Priest, Warlock, Mage, Paladin, Hunter) - // 41011 Martyr Complex: +35 stamina (All classes) - // 41406 Dementia: Every 5 seconds either gives you +5% damage/healing. (Druid, Shaman, Priest, Warlock, Mage, Paladin) - // 41409 Dementia: Every 5 seconds either gives you -5% damage/healing. (Druid, Shaman, Priest, Warlock, Mage, Paladin) - case 39446: - { - if (!IsPlayer() || !IsAlive()) - return false; - - // Select class defined buff - switch (getClass()) - { - case CLASS_PALADIN: // 39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409 - case CLASS_DRUID: // 39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409 - triggered_spell_id = RAND(39511, 40997, 40998, 40999, 41002, 41005, 41009, 41011, 41409); - cooldown_spell_id = 39511; - break; - case CLASS_ROGUE: // 39511, 40997, 40998, 41002, 41005, 41011 - case CLASS_WARRIOR: // 39511, 40997, 40998, 41002, 41005, 41011 - case CLASS_DEATH_KNIGHT: - triggered_spell_id = RAND(39511, 40997, 40998, 41002, 41005, 41011); - cooldown_spell_id = 39511; - break; - case CLASS_PRIEST: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 - case CLASS_SHAMAN: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 - case CLASS_MAGE: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 - case CLASS_WARLOCK: // 40999, 41002, 41005, 41009, 41011, 41406, 41409 - triggered_spell_id = RAND(40999, 41002, 41005, 41009, 41011, 41406, 41409); - cooldown_spell_id = 40999; - break; - case CLASS_HUNTER: // 40997, 40999, 41002, 41005, 41009, 41011, 41406, 41409 - triggered_spell_id = RAND(40997, 40999, 41002, 41005, 41009, 41011, 41406, 41409); - cooldown_spell_id = 40997; - break; - default: - return false; - } - - target = this; - if (roll_chance_i(10)) - ToPlayer()->Say("This is Madness!", LANG_UNIVERSAL); /// @todo: It should be moved to database, shouldn't it? - break; - } - // Sunwell Exalted Caster Neck (??? neck) - // cast ??? Light's Wrath if Exalted by Aldor - // cast ??? Arcane Bolt if Exalted by Scryers - case 46569: - return false; // old unused version - // Sunwell Exalted Caster Neck (Shattered Sun Pendant of Acumen neck) - // cast 45479 Light's Wrath if Exalted by Aldor - // cast 45429 Arcane Bolt if Exalted by Scryers - case 45481: - { - Player* player = ToPlayer(); - if (!player) - return false; - - // Get Aldor reputation rank - if (player->GetReputationRank(932) == REP_EXALTED) - { - target = this; - triggered_spell_id = 45479; - break; - } - // Get Scryers reputation rank - if (player->GetReputationRank(934) == REP_EXALTED) - { - // triggered at positive/self casts also, current attack target used then - if (target && IsFriendlyTo(target)) - { - target = GetVictim(); - if (!target) - { - target = player->GetSelectedUnit(); - if (!target) - return false; - } - if (IsFriendlyTo(target)) - return false; - } - - triggered_spell_id = 45429; - break; - } - return false; - } - // Sunwell Exalted Melee Neck (Shattered Sun Pendant of Might neck) - // cast 45480 Light's Strength if Exalted by Aldor - // cast 45428 Arcane Strike if Exalted by Scryers - case 45482: - { - if (!IsPlayer()) - return false; - - // Get Aldor reputation rank - if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) - { - target = this; - triggered_spell_id = 45480; - break; - } - // Get Scryers reputation rank - if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) - { - triggered_spell_id = 45428; - break; - } - return false; - } - // Sunwell Exalted Tank Neck (Shattered Sun Pendant of Resolve neck) - // cast 45431 Arcane Insight if Exalted by Aldor - // cast 45432 Light's Ward if Exalted by Scryers - case 45483: - { - if (!IsPlayer()) - return false; - - // Get Aldor reputation rank - if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) - { - target = this; - triggered_spell_id = 45432; - break; - } - // Get Scryers reputation rank - if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) - { - target = this; - triggered_spell_id = 45431; - break; - } - return false; - } - // Sunwell Exalted Healer Neck (Shattered Sun Pendant of Restoration neck) - // cast 45478 Light's Salvation if Exalted by Aldor - // cast 45430 Arcane Surge if Exalted by Scryers - case 45484: - { - if (!IsPlayer()) - return false; - - // Get Aldor reputation rank - if (ToPlayer()->GetReputationRank(932) == REP_EXALTED) - { - target = this; - triggered_spell_id = 45478; - break; - } - // Get Scryers reputation rank - if (ToPlayer()->GetReputationRank(934) == REP_EXALTED) - { - triggered_spell_id = 45430; - break; - } - return false; - } - // Kill command - case 58914: - { - // Remove aura stack from pet - RemoveAuraFromStack(58914); - Unit* owner = GetOwner(); - if (!owner) - return true; - // reduce the owner's aura stack - owner->RemoveAuraFromStack(34027); - return true; - } - // Vampiric Touch (generic, used by some boss) - case 52723: - case 60501: - { - triggered_spell_id = 52724; - basepoints0 = damage / 2; - target = this; - break; - } - // Divine purpose - case 31871: - case 31872: - { - // Roll chane - if (!victim || !victim->IsAlive() || !roll_chance_i(triggerAmount)) - return false; - - // Remove any stun effect on target - victim->RemoveAurasWithMechanic(1 << MECHANIC_STUN, AURA_REMOVE_BY_ENEMY_SPELL); - return true; - } - // Glyph of Life Tap - case 63320: - { - triggered_spell_id = 63321; // Life Tap - break; - } - case 71519: // Deathbringer's Will Normal - { - if (!IsPlayer() || HasSpellCooldown(71484)) - return false; - - AddSpellCooldown(71484, 0, cooldown); - - std::vector RandomSpells; - switch (getClass()) - { - case CLASS_WARRIOR: - case CLASS_PALADIN: - case CLASS_DEATH_KNIGHT: - RandomSpells.push_back(71484); - RandomSpells.push_back(71491); - RandomSpells.push_back(71492); - break; - case CLASS_SHAMAN: - case CLASS_ROGUE: - RandomSpells.push_back(71486); - RandomSpells.push_back(71485); - RandomSpells.push_back(71492); - break; - case CLASS_DRUID: - RandomSpells.push_back(71484); - RandomSpells.push_back(71485); - RandomSpells.push_back(71492); - break; - case CLASS_HUNTER: - RandomSpells.push_back(71486); - RandomSpells.push_back(71491); - RandomSpells.push_back(71485); - break; - default: - return false; - } - if (RandomSpells.empty()) // shouldn't happen - return false; - - uint8 rand_spell = irand(0, (RandomSpells.size() - 1)); - CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); - break; - } - case 71562: // Deathbringer's Will Heroic - { - if (!IsPlayer() || HasSpellCooldown(71561)) - return false; - - AddSpellCooldown(71561, 0, cooldown); - - std::vector RandomSpells; - switch (getClass()) - { - case CLASS_WARRIOR: - case CLASS_PALADIN: - case CLASS_DEATH_KNIGHT: - RandomSpells.push_back(71561); - RandomSpells.push_back(71559); - RandomSpells.push_back(71560); - break; - case CLASS_SHAMAN: - case CLASS_ROGUE: - RandomSpells.push_back(71558); - RandomSpells.push_back(71556); - RandomSpells.push_back(71560); - break; - case CLASS_DRUID: - RandomSpells.push_back(71561); - RandomSpells.push_back(71556); - RandomSpells.push_back(71560); - break; - case CLASS_HUNTER: - RandomSpells.push_back(71558); - RandomSpells.push_back(71559); - RandomSpells.push_back(71556); - break; - default: - return false; - } - if (RandomSpells.empty()) // shouldn't happen - return false; - - uint8 rand_spell = irand(0, (RandomSpells.size() - 1)); - CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); - break; - } - // Freya, Petrified Bark - case 62933: - case 62337: - { - if (!victim) - return false; - - int32 dmg = damage; - victim->CastCustomSpell(this, 62379, &dmg, 0, 0, true); - return true; - } - // Trial of the Champion, Earth Shield - case 67534: - { - const int32 dmg = (int32)damage; - CastCustomSpell(this, 67535, &dmg, nullptr, nullptr, true, 0, triggeredByAura, triggeredByAura->GetCasterGUID()); - return true; - } - // Trial of the Crusader, Faction Champions, Retaliation - case 65932: - { - // check attack comes not from behind - if (!victim || !HasInArc(M_PI, victim)) - return false; - - triggered_spell_id = 65934; - break; - } - // Pit of Saron, Tyrannus, Overlord's Brand - case 69172: // everything except for DoTs - { - if (!target) - return false; - if (Unit* caster = triggeredByAura->GetCaster()) - { - if (procFlag & (PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS)) - { - int32 dmg = 5.5f * damage; - target->CastCustomSpell(caster, 69190, &dmg, 0, 0, true); - } - else - { - if (caster->GetVictim()) - { - int32 dmg = damage; - target->CastCustomSpell(caster->GetVictim(), 69189, &dmg, 0, 0, true); - } - } - } - return true; - } - // Pit of Saron, Tyrannus, Overlord's Brand - case 69173: // only DoTs - { - if (!target) - return false; - if (Unit* caster = triggeredByAura->GetCaster()) - { - if (procEx & PROC_EX_INTERNAL_HOT) - { - int32 dmg = 5.5f * damage; - target->CastCustomSpell(caster, 69190, &dmg, 0, 0, true); - } - else - { - if (caster->GetVictim()) - { - int32 dmg = damage; - target->CastCustomSpell(caster->GetVictim(), 69189, &dmg, 0, 0, true); - } - } - } - return true; - } - // Icecrown Citadel, Lady Deathwhisper, Vampiric Might - case 70674: - { - if (Unit* caster = triggeredByAura->GetCaster()) - { - int32 dmg = 3 * damage; - caster->CastCustomSpell(caster, 70677, &dmg, 0, 0, true); - } - return true; - } - // Item: Purified Shard of the Gods - case 69755: - { - triggered_spell_id = ((procFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) ? 69733 : 69729); - break; - } - // Item: Shiny Shard of the Gods - case 69739: - { - triggered_spell_id = ((procFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) ? 69734 : 69730); - break; - } - // VoA: Meteor Fists koralon - case 66725: - case 68161: - { - triggered_spell_id = 66765; // handled by spell_difficulty - break; - } - } - break; - } - case SPELLFAMILY_MAGE: - { - // Magic Absorption - if (dummySpell->SpellIconID == 459) // only this spell has SpellIconID == 459 and dummy aura - { - if (!HasActivePowerType(POWER_MANA)) - return false; - - // mana reward - basepoints0 = CalculatePct(int32(GetMaxPower(POWER_MANA)), triggerAmount); - target = this; - triggered_spell_id = 29442; - break; - } - // Hot Streak - if (dummySpell->SpellIconID == 2999) - { - if (effIndex != 0) - return false; - AuraEffect* counter = triggeredByAura->GetBase()->GetEffect(EFFECT_1); - if (!counter) - return true; - - // Count spell criticals in a row in second aura - if (procEx & PROC_EX_CRITICAL_HIT) - { - counter->SetAmount(counter->GetAmount() * 2); - if (counter->GetAmount() < 100) // not enough - return true; - // Crititcal counted -> roll chance - if (roll_chance_i(triggerAmount)) - CastSpell(this, 48108, true, castItem, triggeredByAura); - } - counter->SetAmount(25); - return true; - } - // Incanter's Regalia set (add trigger chance to Mana Shield) - if (dummySpell->SpellFamilyFlags[0] & 0x8000) - { - if (!IsPlayer()) - return false; - - target = this; - triggered_spell_id = 37436; - break; - } - switch (dummySpell->Id) - { - // Glyph of Polymorph - case 56375: - { - if (!target) - return false; - target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE, ObjectGuid::Empty, target->GetAura(32409)); // SW:D shall not be removed. - target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE_PERCENT); - target->RemoveAurasByType(SPELL_AURA_PERIODIC_LEECH); - return true; - } - // Glyph of Icy Veins - case 56374: - { - RemoveAurasByType(SPELL_AURA_HASTE_SPELLS, ObjectGuid::Empty, 0, true, false); - RemoveAurasByType(SPELL_AURA_MOD_DECREASE_SPEED); - return true; - } - // Glyph of Ice Block - case 56372: - { - Player* player = ToPlayer(); - if (!player) - return false; - - SpellCooldowns const cooldowns = player->GetSpellCooldowns(); - // remove cooldowns on all ranks of Frost Nova - for (SpellCooldowns::const_iterator itr = cooldowns.begin(); itr != cooldowns.end(); ++itr) - { - SpellInfo const* cdSpell = sSpellMgr->GetSpellInfo(itr->first); - // Frost Nova - if (cdSpell && cdSpell->SpellFamilyName == SPELLFAMILY_MAGE - && cdSpell->SpellFamilyFlags[0] & 0x00000040) - player->RemoveSpellCooldown(cdSpell->Id, true); - } - break; - } - } - break; - } - case SPELLFAMILY_WARRIOR: - { - // Second Wind - if (dummySpell->SpellIconID == 1697) - { - // only for spells and hit/crit (trigger start always) and not start from self casted spells (5530 Mace Stun Effect for example) - if (procSpell == 0 || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) - return false; - // Need stun or root mechanic - if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN)))) - return false; - - switch (dummySpell->Id) - { - case 29838: - triggered_spell_id = 29842; - break; - case 29834: - triggered_spell_id = 29841; - break; - case 42770: - triggered_spell_id = 42771; - break; - default: - LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non handled spell id: {} (SW)", dummySpell->Id); - return false; - } - - target = this; - break; - } - break; - } - case SPELLFAMILY_WARLOCK: - { - switch (dummySpell->Id) - { - // Nightfall - case 18094: - case 18095: - // Glyph of corruption - case 56218: - { - target = this; - triggered_spell_id = 17941; - break; - } - // Soul Leech - case 30293: - case 30295: - case 30296: - { - // Improved Soul Leech - AuraEffectList const& SoulLeechAuras = GetAuraEffectsByType(SPELL_AURA_DUMMY); - for (Unit::AuraEffectList::const_iterator i = SoulLeechAuras.begin(); i != SoulLeechAuras.end(); ++i) - { - if ((*i)->GetId() == 54117 || (*i)->GetId() == 54118) - { - if ((*i)->GetEffIndex() != 0) - continue; - basepoints0 = int32((*i)->GetAmount()); - target = GetGuardianPet(); - if (target) - { - // regen mana for pet - CastCustomSpell(target, 54607, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); - } - // regen mana for caster - CastCustomSpell(this, 59117, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); - // Get second aura of spell for replenishment effect on party - if (AuraEffect const* aurEff = (*i)->GetBase()->GetEffect(EFFECT_1)) - { - // Replenishment - roll chance - if (roll_chance_i(aurEff->GetAmount())) - { - CastSpell(this, 57669, true, castItem, triggeredByAura); - } - } - break; - } - } - // health - basepoints0 = CalculatePct(int32(damage), triggerAmount); - target = this; - triggered_spell_id = 30294; - break; - } - // Shadowflame (Voidheart Raiment set bonus) - case 37377: - { - triggered_spell_id = 37379; - break; - } - // Pet Healing (Corruptor Raiment or Rift Stalker Armor) - case 37381: - { - target = GetGuardianPet(); - if (!target) - return false; - - // heal amount - basepoints0 = CalculatePct(int32(damage), triggerAmount); - triggered_spell_id = 37382; - break; - } - // Shadowflame Hellfire (Voidheart Raiment set bonus) - case 39437: - { - triggered_spell_id = 37378; - break; - } - } - break; - } - case SPELLFAMILY_PRIEST: - { - // Body and Soul - if (dummySpell->SpellIconID == 2218) - { - // Proc only from Abolish desease on self cast - if (procSpell->Id != 552 || victim != this || !roll_chance_i(triggerAmount)) - return false; - triggered_spell_id = 64136; - target = this; - break; - } - switch (dummySpell->Id) - { - // Vampiric Embrace - case 15286: - { - if (!victim || !victim->IsAlive() || procSpell->SpellFamilyFlags[1] & 0x80000) - return false; - - // heal amount - int32 total = CalculatePct(int32(damage), triggerAmount); - int32 team = total / 5; - int32 self = total - team; - CastCustomSpell(this, 15290, &team, &self, nullptr, true, castItem, triggeredByAura); - return true; // no hidden cooldown - } - // Priest Tier 6 Trinket (Ashtongue Talisman of Acumen) - case 40438: - { - // Shadow Word: Pain - if (procSpell->SpellFamilyFlags[0] & 0x8000) - triggered_spell_id = 40441; - // Renew - else if (procSpell->SpellFamilyFlags[0] & 0x40) - triggered_spell_id = 40440; - else - return false; - - target = this; - break; - } - // Improved Shadowform - case 47570: - case 47569: - { - if (!roll_chance_i(triggerAmount)) - return false; - - RemoveMovementImpairingAuras(true); - break; - } - // Glyph of Dispel Magic - case 55677: - { - // Dispel Magic shares spellfamilyflag with abolish disease - if (procSpell->SpellIconID != 74) - return false; - if (!target || !target->IsFriendlyTo(this)) - return false; - - basepoints0 = int32(target->CountPctFromMaxHealth(triggerAmount)); - triggered_spell_id = 56131; - break; - } - // Oracle Healing Bonus ("Garments of the Oracle" set) - case 26169: - { - // heal amount - basepoints0 = int32(CalculatePct(damage, 10)); - target = this; - triggered_spell_id = 26170; - break; - } - // Frozen Shadoweave (Shadow's Embrace set) warning! its not only priest set - case 39372: - { - if (!procSpell || (procSpell->GetSchoolMask() & (SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_SHADOW)) == 0) - return false; - - // heal amount - basepoints0 = CalculatePct(int32(damage), triggerAmount); - target = this; - triggered_spell_id = 39373; - break; - } - // Greater Heal (Vestments of Faith (Priest Tier 3) - 4 pieces bonus) - case 28809: - { - triggered_spell_id = 28810; - break; - } - // Priest T10 Healer 2P Bonus - case 70770: - // Flash Heal - if (procSpell->SpellFamilyFlags[0] & 0x800) - { - triggered_spell_id = 70772; - SpellInfo const* blessHealing = sSpellMgr->GetSpellInfo(triggered_spell_id); - if (!blessHealing || !victim) - return false; - basepoints0 = int32(CalculatePct(damage, triggerAmount) / (blessHealing->GetMaxDuration() / blessHealing->Effects[0].Amplitude)); - victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_HEAL, basepoints0); - return true; - } - break; - } - break; - } - case SPELLFAMILY_DRUID: - { - switch (dummySpell->Id) - { - // Glyph of Innervate - case 54832: - { - if (procSpell->SpellIconID != 62) - return false; - - int32 mana_perc = triggeredByAura->GetSpellInfo()->Effects[triggeredByAura->GetEffIndex()].CalcValue(); - basepoints0 = int32(CalculatePct(GetCreatePowers(POWER_MANA), mana_perc) / 10); - triggered_spell_id = 54833; - target = this; - break; - } - // Glyph of Starfire - case 54845: - { - triggered_spell_id = 54846; - break; - } - // Glyph of Shred - case 54815: - { - if (!target) - return false; - - // try to find spell Rip on the target - if (AuraEffect const* AurEff = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x00800000, 0x0, 0x0, GetGUID())) - { - // Rip's max duration, note: spells which modifies Rip's duration also counted like Glyph of Rip - uint32 CountMin = AurEff->GetBase()->GetMaxDuration(); - - // just Rip's max duration without other spells - uint32 CountMax = AurEff->GetSpellInfo()->GetMaxDuration(); - - // add possible auras' and Glyph of Shred's max duration - CountMax += 3 * triggerAmount * IN_MILLISECONDS; // Glyph of Shred -> +6 seconds - CountMax += HasAura(54818) ? 4 * IN_MILLISECONDS : 0; // Glyph of Rip -> +4 seconds - CountMax += HasAura(60141) ? 4 * IN_MILLISECONDS : 0; // Rip Duration/Lacerate Damage -> +4 seconds - - // if min < max -> that means caster didn't cast 3 shred yet - // so set Rip's duration and max duration - if (CountMin < CountMax) - { - AurEff->GetBase()->SetDuration(AurEff->GetBase()->GetDuration() + triggerAmount * IN_MILLISECONDS); - AurEff->GetBase()->SetMaxDuration(CountMin + triggerAmount * IN_MILLISECONDS); - return true; - } - } - // if not found Rip - return false; - } - // Glyph of Rake - case 54821: - { - if (procSpell->SpellVisual[0] == 750 && procSpell->Effects[1].ApplyAuraName == 3) - { - if (target && target->IsCreature()) - { - triggered_spell_id = 54820; - break; - } - } - return false; - } - // Leader of the Pack - case 24932: - { - if (triggerAmount <= 0) - return false; - basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); - target = this; - triggered_spell_id = 34299; - if (triggeredByAura->GetCasterGUID() != GetGUID()) - break; - int32 basepoints1 = CalculatePct(GetMaxPower(Powers(POWER_MANA)), triggerAmount * 2); - // Improved Leader of the Pack - // Check cooldown of heal spell cooldown - if (IsPlayer() && !ToPlayer()->HasSpellCooldown(34299)) - CastCustomSpell(this, 68285, &basepoints1, 0, 0, true, 0, triggeredByAura); - break; - } - // Healing Touch (Dreamwalker Raiment set) - case 28719: - { - // mana back - basepoints0 = int32(CalculatePct(spellProc->GetPowerCost(), 30)); - target = this; - triggered_spell_id = 28742; - break; - } - // Glyph of Rejuvenation - case 54754: - { - if (!victim || !victim->HealthBelowPct(uint32(triggerAmount))) - return false; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - triggered_spell_id = 54755; - break; - } - // Healing Touch Refund (Idol of Longevity trinket) - case 28847: - { - target = this; - triggered_spell_id = 28848; - break; - } - // Mana Restore (Malorne Raiment set / Malorne Regalia set) - case 37288: - case 37295: - { - target = this; - triggered_spell_id = 37238; - break; - } - // Druid Tier 6 Trinket - case 40442: - { - float chance; - - // Starfire - if (procSpell->SpellFamilyFlags[0] & 0x4) - { - triggered_spell_id = 40445; - chance = 25.0f; - } - // Rejuvenation - else if (procSpell->SpellFamilyFlags[0] & 0x10) - { - triggered_spell_id = 40446; - chance = 25.0f; - } - // Mangle (Bear) and Mangle (Cat) - else if (procSpell->SpellFamilyFlags[1] & 0x00000440) - { - triggered_spell_id = 40452; - chance = 40.0f; - } - else - return false; - - if (!roll_chance_f(chance)) - return false; - - target = this; - break; - } - // Maim Interrupt - case 44835: - { - // Deadly Interrupt Effect - triggered_spell_id = 32747; - break; - } - // Item - Druid T10 Restoration 4P Bonus (Rejuvenation) - case 70664: - { - // xinef: proc only from normal Rejuvenation, and proc rejuvenation - if (!victim || !procSpell || procSpell->SpellIconID != 64) - return false; - - Player* caster = ToPlayer(); - if (!caster) - return false; - if (!caster->GetGroup() && victim == this) - return false; - - CastCustomSpell(70691, SPELLVALUE_BASE_POINT0, damage, victim, true); - return true; - } - } - // Eclipse - if (dummySpell->SpellIconID == 2856 && IsPlayer()) - { - if (!procSpell || effIndex != 0) - return false; - - bool isWrathSpell = (procSpell->SpellFamilyFlags[0] & 1); - - if (!roll_chance_f(dummySpell->ProcChance * (isWrathSpell ? 0.6f : 1.0f))) - return false; - - target = this; - if (target->HasAura(isWrathSpell ? 48517 : 48518)) - return false; - - triggered_spell_id = isWrathSpell ? 48518 : 48517; - break; - } - [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. - } - case SPELLFAMILY_ROGUE: - { - switch (dummySpell->Id) - { - // Glyph of Backstab - case 56800: - { - if (victim) - if (AuraEffect* aurEff = victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x100000, 0, 0, GetGUID())) - if (Aura* aur = aurEff->GetBase()) - if (!aur->IsRemoved() && aur->GetDuration() > 0) - if ((aur->GetApplyTime() + aur->GetMaxDuration() / 1000 + 5) > (GameTime::GetGameTime().count() + aur->GetDuration() / 1000)) - { - aur->SetDuration(aur->GetDuration() + 2000); - return true; - } - return false; - } - // Deadly Throw Interrupt - case 32748: - { - // Prevent cast Deadly Throw Interrupt on self from last effect (apply dummy) of Deadly Throw - if (this == victim) - return false; - - triggered_spell_id = 32747; - break; - } - } - // Master of subtlety - if (dummySpell->SpellIconID == 2114) - { - triggered_spell_id = 31665; - basepoints0 = triggerAmount; - break; - } - // Cut to the Chase - if (dummySpell->SpellIconID == 2909) - { - // "refresh your Slice and Dice duration to its 5 combo point maximum" - // lookup Slice and Dice - if (AuraEffect const* aur = GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000, 0, 0)) - { - aur->GetBase()->SetDuration(aur->GetSpellInfo()->GetMaxDuration(), true); - return true; - } - return false; - } - // Deadly Brew - else if (dummySpell->SpellIconID == 2963) - { - triggered_spell_id = 3409; - break; - } - // Quick Recovery - else if (dummySpell->SpellIconID == 2116) - { - if (!procSpell) - return false; - - // energy cost save - basepoints0 = CalculatePct(int32(procSpell->ManaCost), triggerAmount); - if (basepoints0 <= 0) - return false; - - target = this; - triggered_spell_id = 31663; - break; - } - break; - } - case SPELLFAMILY_HUNTER: - { - switch (dummySpell->SpellIconID) - { - case 2236: // Thrill of the Hunt - { - if (!procSpell) - return false; - - Spell* spell = ToPlayer()->m_spellModTakingSpell; - - // Disable charge drop because of Lock and Load - if (spell) - ToPlayer()->SetSpellModTakingSpell(spell, false); - - // Explosive Shot - if (procSpell->SpellFamilyFlags[2] & 0x200) - { - if (!victim) - return false; - if (AuraEffect const* pEff = victim->GetAuraEffect(SPELL_AURA_PERIODIC_DUMMY, SPELLFAMILY_HUNTER, 0x0, 0x80000000, 0x0, GetGUID())) - basepoints0 = pEff->GetSpellInfo()->CalcPowerCost(this, SpellSchoolMask(pEff->GetSpellInfo()->SchoolMask)) * 4 / 10 / 3; - } - else - basepoints0 = procSpell->CalcPowerCost(this, SpellSchoolMask(procSpell->SchoolMask)) * 4 / 10; - - if (spell) - ToPlayer()->SetSpellModTakingSpell(spell, true); - - if (basepoints0 <= 0) - return false; - - target = this; - triggered_spell_id = 34720; - break; - } - case 3406: // Hunting Party - { - triggered_spell_id = 57669; - target = this; - break; - } - case 3560: // Rapid Recuperation - { - // This effect only from Rapid Killing (mana regen) - if (!(procSpell->SpellFamilyFlags[1] & 0x01000000)) - return false; - - target = this; - - switch (dummySpell->Id) - { - case 53228: // Rank 1 - triggered_spell_id = 56654; - break; - case 53232: // Rank 2 - triggered_spell_id = 58882; - break; - } - break; - } - } - - switch (dummySpell->Id) - { - case 57870: // Glyph of Mend Pet - { - if (!victim) - return false; - - victim->CastSpell(victim, 57894, true, nullptr, nullptr, GetGUID()); - return true; - } - } - break; - } - case SPELLFAMILY_PALADIN: - { - // Light's Beacon - Beacon of Light - if (dummySpell->Id == 53651) - { - if (!victim) - return false; - - // Do not proc from Glyph of Holy Light and Judgement of Light - if (procSpell->Id == 20267 || procSpell->Id == 54968) - { - return false; - } - - Unit* beaconTarget = triggeredByAura->GetBase()->GetCaster(); - if (!beaconTarget || beaconTarget == this || !beaconTarget->GetAura(53563, victim->GetGUID())) - return false; - - basepoints0 = int32(damage); - triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654; - - victim->CastCustomSpell(beaconTarget, triggered_spell_id, &basepoints0, nullptr, nullptr, true, 0, triggeredByAura, victim->GetGUID()); - return true; - } - // Judgements of the Wise - if (dummySpell->SpellIconID == 3017) - { - target = this; - triggered_spell_id = 31930; - // replenishment - CastSpell(this, 57669, true, castItem, triggeredByAura); - break; - } - // Righteous Vengeance - if (dummySpell->SpellIconID == 3025) - { - if (!victim) - return false; - - // 4 damage tick - basepoints0 = triggerAmount * damage / 400; - triggered_spell_id = 61840; - // Add remaining ticks to damage done - victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); - return true; - } - // Sheath of Light - if (dummySpell->SpellIconID == 3030) - { - // 4 healing tick - basepoints0 = triggerAmount * damage / 400; - triggered_spell_id = 54203; - break; - } - switch (dummySpell->Id) - { - // Judgement of Light - case 20185: - { - if (!victim || !victim->IsAlive()) - return false; - - auto* caster = triggeredByAura->GetBase()->GetCaster(); - if (!caster || !victim->IsFriendlyTo(caster)) - return false; - - // 2% of base health - basepoints0 = int32(victim->CountPctFromMaxHealth(2)); - victim->CastCustomSpell(victim, 20267, &basepoints0, 0, 0, true, 0, triggeredByAura); - return true; - } - // Judgement of Wisdom - case 20186: - { - if (!victim || !victim->IsAlive() || !victim->HasActivePowerType(POWER_MANA)) - return false; - - auto* caster = triggeredByAura->GetBase()->GetCaster(); - if (!caster || !victim->IsFriendlyTo(caster)) - return false; - - // 2% of base mana - basepoints0 = int32(CalculatePct(victim->GetCreateMana(), 2)); - victim->CastCustomSpell(victim, 20268, &basepoints0, nullptr, nullptr, true, 0, triggeredByAura); - return true; - } - // Holy Power (Redemption Armor set) - case 28789: - { - if (!victim) - return false; - - // Set class defined buff - switch (victim->getClass()) - { - case CLASS_PALADIN: - case CLASS_PRIEST: - case CLASS_SHAMAN: - case CLASS_DRUID: - triggered_spell_id = 28795; // Increases the friendly target's mana regeneration by $s1 per 5 sec. for $d. - break; - case CLASS_MAGE: - case CLASS_WARLOCK: - triggered_spell_id = 28793; // Increases the friendly target's spell damage and healing by up to $s1 for $d. - break; - case CLASS_HUNTER: - case CLASS_ROGUE: - triggered_spell_id = 28791; // Increases the friendly target's attack power by $s1 for $d. - break; - case CLASS_WARRIOR: - triggered_spell_id = 28790; // Increases the friendly target's armor - break; - default: - return false; - } - break; - } - // Seal of Vengeance (damage calc on apply aura) - case 31801: - { - if (effIndex != 0 || !victim) // effect 1, 2 used by seal unleashing code - return false; - - // At melee attack or Hammer of the Righteous spell damage considered as melee attack - bool stacker = !procSpell || procSpell->Id == 53595; - // spells with SPELL_DAMAGE_CLASS_MELEE excluding Judgements - bool damager = procSpell && (procSpell->EquippedItemClass != -1 || (procSpell->SpellIconID == 243 && procSpell->SpellVisual[0] == 39)); - - if (!stacker && !damager) - return false; - - triggered_spell_id = 31803; - - if (Aura* aur = victim->GetAura(triggered_spell_id, GetGUID())) - { - if (aur->GetStackAmount() == 5) - { - if (stacker) - aur->RefreshDuration(); - } - } - - CastSpell(victim, 42463, true, castItem, triggeredByAura); // Seal of Vengeance - - if (!stacker) - return false; - break; - } - // Seal of Corruption - case 53736: - { - if (effIndex != 0 || !victim) // effect 1, 2 used by seal unleashing code - return false; - - // At melee attack or Hammer of the Righteous spell damage considered as melee attack - bool stacker = !procSpell || procSpell->Id == 53595; - // spells with SPELL_DAMAGE_CLASS_MELEE excluding Judgements - bool damager = procSpell && (procSpell->EquippedItemClass != -1 || (procSpell->SpellIconID == 243 && procSpell->SpellVisual[0] == 39)); - - if (!stacker && !damager) - return false; - - triggered_spell_id = 53742; - - if (Aura* aur = victim->GetAura(triggered_spell_id, GetGUID())) - { - if (aur->GetStackAmount() == 5) - { - if (stacker) - aur->RefreshDuration(); - } - } - - CastSpell(victim, 53739, true, castItem, triggeredByAura); // Seal of Corruption - - if (!stacker) - return false; - break; - } - // Spiritual Attunement - case 31785: - case 33776: - { - // if healed by another unit (victim) - if (this == victim) - return false; - - // dont allow non-positive dots to proc - if (!procSpell || !procSpell->IsPositive()) - return false; - - HealInfo const* healInfo = eventInfo.GetHealInfo(); - if (!healInfo) - { - return false; - } - - uint32 effectiveHeal = healInfo->GetEffectiveHeal(); - if (effectiveHeal) - { - // heal amount - basepoints0 = int32(CalculatePct(effectiveHeal, triggerAmount)); - target = this; - - if (basepoints0) - triggered_spell_id = 31786; - } - break; - } - // Paladin Tier 6 Trinket (Ashtongue Talisman of Zeal) - case 40470: - { - if (!procSpell) - return false; - - float chance = 0.0f; - - // Flash of light/Holy light - if (procSpell->SpellFamilyFlags[0] & 0xC0000000) - { - triggered_spell_id = 40471; - chance = 15.0f; - } - // Judgement (any) - else if (procSpell->SpellFamilyFlags[0] & 0x800000) - { - triggered_spell_id = 40472; - chance = 50.0f; - } - else - return false; - - if (!roll_chance_f(chance)) - return false; - - break; - } - // Glyph of Holy Light - case 54937: - { - triggered_spell_id = 54968; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - break; - } - // Item - Paladin T8 Holy 2P Bonus - case 64890: - { - triggered_spell_id = 64891; - basepoints0 = triggerAmount * damage / 300; - break; - } - case 71406: // Tiny Abomination in a Jar - case 71545: // Tiny Abomination in a Jar (Heroic) - { - if (!victim || !victim->IsAlive()) - return false; - - CastSpell(this, 71432, true, nullptr, triggeredByAura); - - Aura const* dummy = GetAura(71432); - if (!dummy || dummy->GetStackAmount() < (dummySpell->Id == 71406 ? 8 : 7)) - return false; - - RemoveAurasDueToSpell(71432); - triggered_spell_id = 71433; // default main hand attack - // roll if offhand - if (Player const* player = ToPlayer()) - if (player->GetWeaponForAttack(OFF_ATTACK, true) && urand(0, 1)) - triggered_spell_id = 71434; - target = victim; - break; - } - // Item - Icecrown 25 Normal Dagger Proc - case 71880: - { - switch (getPowerType()) - { - case POWER_MANA: - triggered_spell_id = 71881; - break; - case POWER_RAGE: - triggered_spell_id = 71883; - break; - case POWER_ENERGY: - triggered_spell_id = 71882; - break; - case POWER_RUNIC_POWER: - triggered_spell_id = 71884; - break; - default: - return false; - } - break; - } - // Item - Icecrown 25 Heroic Dagger Proc - case 71892: - { - switch (getPowerType()) - { - case POWER_MANA: - triggered_spell_id = 71888; - break; - case POWER_RAGE: - triggered_spell_id = 71886; - break; - case POWER_ENERGY: - triggered_spell_id = 71887; - break; - case POWER_RUNIC_POWER: - triggered_spell_id = 71885; - break; - default: - return false; - } - break; - } - } - break; - } - case SPELLFAMILY_SHAMAN: - { - switch (dummySpell->Id) - { - // Tidal Force - case 55198: - { - // Remove aura stack from caster - RemoveAuraFromStack(55166); - // drop charges - return false; - } - // Totemic Power (The Earthshatterer set) - case 28823: - { - if (!victim) - return false; - - // Set class defined buff - switch (victim->getClass()) - { - case CLASS_PALADIN: - case CLASS_PRIEST: - case CLASS_SHAMAN: - case CLASS_DRUID: - triggered_spell_id = 28824; // Increases the friendly target's mana regeneration by $s1 per 5 sec. for $d. - break; - case CLASS_MAGE: - case CLASS_WARLOCK: - triggered_spell_id = 28825; // Increases the friendly target's spell damage and healing by up to $s1 for $d. - break; - case CLASS_HUNTER: - case CLASS_ROGUE: - triggered_spell_id = 28826; // Increases the friendly target's attack power by $s1 for $d. - break; - case CLASS_WARRIOR: - triggered_spell_id = 28827; // Increases the friendly target's armor - break; - default: - return false; - } - break; - } - // Lesser Healing Wave (Totem of Flowing Water Relic) - case 28849: - { - target = this; - triggered_spell_id = 28850; - break; - } - // Windfury Weapon (Passive) 1-8 Ranks - case 33757: - { - Player* player = ToPlayer(); - if (!player || !castItem || !castItem->IsEquipped() || !victim || !victim->IsAlive()) - return false; - - if (triggeredByAura->GetBase() && castItem->GetGUID() != triggeredByAura->GetBase()->GetCastItemGUID()) - return false; - - WeaponAttackType attType = player->GetAttackBySlot(castItem->GetSlot()); - if ((attType != BASE_ATTACK && attType != OFF_ATTACK) - || (attType == BASE_ATTACK && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) - || (attType == OFF_ATTACK && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK)) - return false; - - // Now amount of extra power stored in 1 effect of Enchant spell - // Get it by item enchant id - uint32 spellId; - switch (castItem->GetEnchantmentId(EnchantmentSlot(TEMP_ENCHANTMENT_SLOT))) - { - case 283: - spellId = 8232; - break; // 1 Rank - case 284: - spellId = 8235; - break; // 2 Rank - case 525: - spellId = 10486; - break; // 3 Rank - case 1669: - spellId = 16362; - break; // 4 Rank - case 2636: - spellId = 25505; - break; // 5 Rank - case 3785: - spellId = 58801; - break; // 6 Rank - case 3786: - spellId = 58803; - break; // 7 Rank - case 3787: - spellId = 58804; - break; // 8 Rank - default: - { - LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non handled item enchantment (rank?) {} for spell id: {} (Windfury)", - castItem->GetEnchantmentId(EnchantmentSlot(TEMP_ENCHANTMENT_SLOT)), dummySpell->Id); - return false; - } - } - - SpellInfo const* windfurySpellInfo = sSpellMgr->GetSpellInfo(spellId); - if (!windfurySpellInfo) - { - LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: non-existing spell id: {} (Windfury)", spellId); - return false; - } - - int32 extra_attack_power = CalculateSpellDamage(victim, windfurySpellInfo, 1); - - // Value gained from additional AP - basepoints0 = int32(extra_attack_power / 14.0f * GetAttackTime(attType) / 1000); - - if (procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK) - triggered_spell_id = 25504; - - if (procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) - triggered_spell_id = 33750; - - // custom cooldown processing case - if (player->HasSpellCooldown(dummySpell->Id)) - return false; - - // apply cooldown before cast to prevent processing itself - player->AddSpellCooldown(dummySpell->Id, 0, 3 * IN_MILLISECONDS); - - // Attack Twice - for (uint32 i = 0; i < 2; ++i) - CastCustomSpell(victim, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); - - return true; - } - // Shaman Tier 6 Trinket - case 40463: - { - if (!procSpell) - return false; - - float chance; - if (procSpell->SpellFamilyFlags[0] & 0x1) - { - triggered_spell_id = 40465; // Lightning Bolt - chance = 15.0f; - } - else if (procSpell->SpellFamilyFlags[0] & 0x80) - { - triggered_spell_id = 40465; // Lesser Healing Wave - chance = 10.0f; - } - else if (procSpell->SpellFamilyFlags[1] & 0x00000010) - { - triggered_spell_id = 40466; // Stormstrike - chance = 50.0f; - } - else - return false; - - if (!roll_chance_f(chance)) - return false; - - target = this; - break; - } - // Glyph of Healing Wave - case 55440: - { - // Not proc from self heals - if (this == victim) - return false; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - target = this; - triggered_spell_id = 55533; - break; - } - // Spirit Hunt - case 58877: - { - // Cast on owner - target = GetOwner(); - if (!target) - return false; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - triggered_spell_id = 58879; - // Heal wolf - CastCustomSpell(this, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); - break; - } - // Shaman T9 Elemental 4P Bonus - case 67228: - { - // Lava Burst - if (procSpell->SpellFamilyFlags[1] & 0x1000) - { - triggered_spell_id = 71824; - SpellInfo const* triggeredSpell = sSpellMgr->GetSpellInfo(triggered_spell_id); - if (!triggeredSpell) - return false; - basepoints0 = CalculatePct(int32(damage), triggerAmount) / (triggeredSpell->GetMaxDuration() / triggeredSpell->Effects[0].Amplitude); - } - break; - } - // Item - Shaman T10 Elemental 4P Bonus - case 70817: - { - if (!target) - return false; - // try to find spell Flame Shock on the target - if (AuraEffect const* aurEff = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_SHAMAN, 0x10000000, 0x0, 0x0, GetGUID())) - { - Aura* flameShock = aurEff->GetBase(); - int32 extraTime = 2 * aurEff->GetAmplitude(); - flameShock->SetMaxDuration(flameShock->GetMaxDuration() + extraTime); - flameShock->SetDuration(flameShock->GetDuration() + extraTime); - - return true; - } - // if not found Flame Shock - return false; - } - break; - } - // Frozen Power - if (dummySpell->SpellIconID == 3780) - { - if (!target) - return false; - if (GetDistance(target) < 15.0f) - return false; - float chance = (float)triggerAmount; - if (!roll_chance_f(chance)) - return false; - - triggered_spell_id = 63685; - break; - } - // Ancestral Awakening - if (dummySpell->SpellIconID == 3065) - { - triggered_spell_id = 52759; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - target = this; - break; - } - // Flametongue Weapon (Passive) - if (dummySpell->SpellFamilyFlags[0] & 0x200000) - { - if (!IsPlayer() || !victim || !victim->IsAlive() || !castItem || !castItem->IsEquipped()) - return false; - - WeaponAttackType attType = Player::GetAttackBySlot(castItem->GetSlot()); - if ((attType != BASE_ATTACK && attType != OFF_ATTACK) - || (attType == BASE_ATTACK && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) - || (attType == OFF_ATTACK && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK)) - return false; - - float fire_onhit = float(CalculatePct(dummySpell->Effects[EFFECT_0]. CalcValue(), 1.0f)); - - float add_spellpower = (float)(SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE) - + victim->SpellBaseDamageBonusTaken(SPELL_SCHOOL_MASK_FIRE)); - - // 1.3speed = 5%, 2.6speed = 10%, 4.0 speed = 15%, so, 1.0speed = 3.84% - ApplyPct(add_spellpower, 3.84f); - - // Enchant on Off-Hand and ready? - if (castItem->GetSlot() == EQUIPMENT_SLOT_OFFHAND && procFlag & PROC_FLAG_DONE_OFFHAND_ATTACK) - { - float BaseWeaponSpeed = GetAttackTime(OFF_ATTACK) / 1000.0f; - - // Value1: add the tooltip damage by swingspeed + Value2: add spelldmg by swingspeed - basepoints0 = int32((fire_onhit * BaseWeaponSpeed) + (add_spellpower * BaseWeaponSpeed)); - triggered_spell_id = 10444; - } - - // Enchant on Main-Hand and ready? - else if (castItem->GetSlot() == EQUIPMENT_SLOT_MAINHAND && procFlag & PROC_FLAG_DONE_MAINHAND_ATTACK) - { - float BaseWeaponSpeed = GetAttackTime(BASE_ATTACK) / 1000.0f; - - // Value1: add the tooltip damage by swingspeed + Value2: add spelldmg by swingspeed - basepoints0 = int32((fire_onhit * BaseWeaponSpeed) + (add_spellpower * BaseWeaponSpeed)); - triggered_spell_id = 10444; - } - - // If not ready, we should return, shouldn't we?! - else - return false; - - CastCustomSpell(victim, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); - return true; - } - // Improved Water Shield - if (dummySpell->SpellIconID == 2287) - { - if (!procSpell) - return false; - - // Default chance for Healing Wave and Riptide - float chance = (float)triggeredByAura->GetAmount(); - - if (procSpell->SpellFamilyFlags[0] & 0x80) - // Lesser Healing Wave - 0.6 of default - chance *= 0.6f; - else if (procSpell->SpellFamilyFlags[0] & 0x100) - // Chain heal - 0.3 of default - chance *= 0.3f; - - if (!roll_chance_f(chance)) - return false; - - // Water Shield - if (AuraEffect const* aurEff = GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0, 0x00000020, 0)) - { - uint32 spell = aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell; - CastSpell(this, spell, true, castItem, triggeredByAura); - return true; - } - return false; - } - // Lightning Overload - if (dummySpell->SpellIconID == 2018) // only this spell have SpellFamily Shaman SpellIconID == 2018 and dummy aura - { - if (!procSpell || !IsPlayer() || !victim) - return false; - - uint32 spell = 45284; - - // chain lightning only procs 1/3 of the time - if (procSpell->SpellFamilyFlags[0] & 0x2) - { - if (!roll_chance_i(33)) - return false; - spell = 45297; - } - - if (procEx & PROC_EX_CRITICAL_HIT) - damage /= 2; - - // do not reduce damage-spells have correct basepoints - damage /= 2; - int32 dmg = damage; - - // Cast - CastCustomSpell(victim, spell, &dmg, 0, 0, true, castItem, triggeredByAura); - return true; - } - // Static Shock - if (dummySpell->SpellIconID == 3059) - { - // Lightning Shield - if (AuraEffect const* aurEff = GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x400, 0, 0)) - { - uint32 spell = sSpellMgr->GetSpellWithRank(26364, aurEff->GetSpellInfo()->GetRank()); - CastSpell(target, spell, true, castItem, triggeredByAura); - aurEff->GetBase()->DropCharge(); - return true; - } - return false; - } - break; - } - case SPELLFAMILY_DEATHKNIGHT: - { - // Improved Blood Presence - if (dummySpell->SpellIconID == 2636) - { - if (!IsPlayer()) - return false; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - break; - } - // Butchery - if (dummySpell->SpellIconID == 2664) - { - basepoints0 = triggerAmount; - triggered_spell_id = 50163; - target = this; - break; - } - // Mark of Blood - if (dummySpell->Id == 49005) - { - /// @todo: need more info (cooldowns/PPM) - triggered_spell_id = 61607; - break; - } - // Unholy Blight - if (dummySpell->Id == 49194) - { - triggered_spell_id = 50536; - SpellInfo const* unholyBlight = sSpellMgr->GetSpellInfo(triggered_spell_id); - if (!unholyBlight || !victim) - return false; - - basepoints0 = CalculatePct(int32(damage), triggerAmount); - - //Glyph of Unholy Blight - if (AuraEffect* glyph = GetAuraEffect(63332, 0)) - AddPct(basepoints0, glyph->GetAmount()); - - basepoints0 = basepoints0 / (unholyBlight->GetMaxDuration() / unholyBlight->Effects[0].Amplitude); - victim->CastDelayedSpellWithPeriodicAmount(this, triggered_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); - return true; - } - // Vendetta - if (dummySpell->SpellFamilyFlags[0] & 0x10000) - { - basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); - triggered_spell_id = 50181; - target = this; - break; - } - // Necrosis - if (dummySpell->SpellIconID == 2709) - { - basepoints0 = CalculatePct(int32(damage), triggerAmount); - triggered_spell_id = 51460; - break; - } - // Threat of Thassarian - if (dummySpell->SpellIconID == 2023) - { - // Must Dual Wield - if (!procSpell || !HasOffhandWeaponForAttack()) - return false; - // Chance as basepoints for dummy aura - if (!roll_chance_i(triggerAmount)) - return false; - - switch (procSpell->Id) - { - // Obliterate - case 49020: - triggered_spell_id = 66198; - break; // Rank 1 - case 51423: - triggered_spell_id = 66972; - break; // Rank 2 - case 51424: - triggered_spell_id = 66973; - break; // Rank 3 - case 51425: - triggered_spell_id = 66974; - break; // Rank 4 - - // Frost Strike - case 49143: - triggered_spell_id = 66196; - break; // Rank 1 - case 51416: - triggered_spell_id = 66958; - break; // Rank 2 - case 51417: - triggered_spell_id = 66959; - break; // Rank 3 - case 51418: - triggered_spell_id = 66960; - break; // Rank 4 - case 51419: - triggered_spell_id = 66961; - break; // Rank 5 - case 55268: - triggered_spell_id = 66962; - break; // Rank 6 - - // Plague Strike - case 45462: - triggered_spell_id = 66216; - break; // Rank 1 - case 49917: - triggered_spell_id = 66988; - break; // Rank 2 - case 49918: - triggered_spell_id = 66989; - break; // Rank 3 - case 49919: - triggered_spell_id = 66990; - break; // Rank 4 - case 49920: - triggered_spell_id = 66991; - break; // Rank 5 - case 49921: - triggered_spell_id = 66992; - break; // Rank 6 - - // Death Strike - case 49998: - triggered_spell_id = 66188; - break; // Rank 1 - case 49999: - triggered_spell_id = 66950; - break; // Rank 2 - case 45463: - triggered_spell_id = 66951; - break; // Rank 3 - case 49923: - triggered_spell_id = 66952; - break; // Rank 4 - case 49924: - triggered_spell_id = 66953; - break; // Rank 5 - - // Rune Strike - case 56815: - triggered_spell_id = 66217; - break; // Rank 1 - - // Blood Strike - case 45902: - triggered_spell_id = 66215; - break; // Rank 1 - case 49926: - triggered_spell_id = 66975; - break; // Rank 2 - case 49927: - triggered_spell_id = 66976; - break; // Rank 3 - case 49928: - triggered_spell_id = 66977; - break; // Rank 4 - case 49929: - triggered_spell_id = 66978; - break; // Rank 5 - case 49930: - triggered_spell_id = 66979; - break; // Rank 6 - default: - return false; - } - - // This should do, restore spell mod so next attack can also use this! - // crit chance for first strike is already computed - ToPlayer()->RestoreSpellMods(m_currentSpells[CURRENT_GENERIC_SPELL], 51124, nullptr); // Killing Machine - ToPlayer()->RestoreSpellMods(m_currentSpells[CURRENT_GENERIC_SPELL], 49796, nullptr); // Deathchill - - // Xinef: Somehow basepoints are divided by 2 which is later divided by 2 (offhand multiplier) - SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); - if (triggerEntry->SchoolMask & SPELL_SCHOOL_MASK_NORMAL) - basepoints0 = triggerEntry->Effects[EFFECT_0].BasePoints * 2; - - SetCantProc(true); - if (basepoints0) - CastCustomSpell(target, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); - else - CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); - SetCantProc(false); - return true; - } - // Runic Power Back on Snare/Root - if (dummySpell->Id == 61257) - { - // only for spells and hit/crit (trigger start always) and not start from self casted spells - if (procSpell == 0 || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) - return false; - // Need snare or root mechanic - if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_SNARE)))) - return false; - triggered_spell_id = 61258; - target = this; - break; - } - // Sudden Doom - if (dummySpell->SpellIconID == 1939 && IsPlayer()) - { - SpellChainNode const* chain = nullptr; - // get highest rank of the Death Coil spell - PlayerSpellMap const& sp_list = ToPlayer()->GetSpellMap(); - for (PlayerSpellMap::const_iterator itr = sp_list.begin(); itr != sp_list.end(); ++itr) - { - // check if shown in spell book - if (!itr->second->Active || !itr->second->IsInSpec(ToPlayer()->GetActiveSpec()) || itr->second->State == PLAYERSPELL_REMOVED) - continue; - - SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(itr->first); - if (!spellProto) - continue; - - if (spellProto->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT - && spellProto->SpellFamilyFlags[0] & 0x2000) - { - SpellChainNode const* newChain = sSpellMgr->GetSpellChainNode(itr->first); - - // No chain entry or entry lower than found entry - if (!chain || !newChain || (chain->rank < newChain->rank)) - { - triggered_spell_id = itr->first; - chain = newChain; - } - else - continue; - // Found spell is last in chain - do not need to look more - // Optimisation for most common case - if (chain && chain->last->Id == itr->first) - break; - } - } - } - break; - } - case SPELLFAMILY_POTION: - { - // alchemist's stone - if (dummySpell->Id == 17619) - { - if (procSpell->SpellFamilyName == SPELLFAMILY_POTION) - { - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; i++) - { - if (procSpell->Effects[i].Effect == SPELL_EFFECT_HEAL) - { - triggered_spell_id = 21399; - } - else if (procSpell->Effects[i].Effect == SPELL_EFFECT_ENERGIZE) - { - triggered_spell_id = 21400; - } - else - continue; - - basepoints0 = int32(CalculateSpellDamage(this, procSpell, i) * 0.4f); - CastCustomSpell(this, triggered_spell_id, &basepoints0, nullptr, nullptr, true, nullptr, triggeredByAura); - } - return true; - } - } - break; - } - case SPELLFAMILY_PET: - { - switch (dummySpell->SpellIconID) - { - // Guard Dog - case 201: - { - if (!victim) - return false; - - triggered_spell_id = 54445; - target = this; - float addThreat = float(CalculatePct(procSpell->Effects[0].CalcValue(this), triggerAmount)); - victim->AddThreat(this, addThreat); - break; - } - // Silverback - case 1582: - triggered_spell_id = dummySpell->Id == 62765 ? 62801 : 62800; - target = this; - break; - } - break; - } - default: - break; - } - - // if not handled by custom case, get triggered spell from dummySpell proto - if (!triggered_spell_id) - triggered_spell_id = dummySpell->Effects[triggeredByAura->GetEffIndex()].TriggerSpell; - - // processed charge only counting case - if (!triggered_spell_id) - return true; - - SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); - if (!triggerEntry) - { - LOG_ERROR("entities.unit", "Unit::HandleDummyAuraProc: Spell {} has non-existing triggered spell {}", dummySpell->Id, triggered_spell_id); - return false; - } - - if (cooldown_spell_id == 0) - cooldown_spell_id = triggered_spell_id; - - if (cooldown) - { - if (HasSpellCooldown(cooldown_spell_id)) - return false; - - AddSpellCooldown(cooldown_spell_id, 0, cooldown); - } - - if (basepoints0) - CastCustomSpell(target, triggered_spell_id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura, originalCaster); - else - CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); - - return true; -} - -// Used in case when access to whole aura is needed -// All procs should be handled like this... -bool Unit::HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, SpellInfo const* /*procSpell*/, uint32 /*procFlag*/, uint32 procEx, uint32 cooldown, bool* handled) -{ - SpellInfo const* dummySpell = triggeredByAura->GetSpellInfo(); - - switch (dummySpell->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - switch (dummySpell->Id) - { - // Nevermelting Ice Crystal - case 71564: - RemoveAuraFromStack(71564); - *handled = true; - break; - // Gaseous Bloat - case 70672: - case 72455: - case 72832: - case 72833: - { - if (Unit* caster = triggeredByAura->GetCaster()) - if (victim && caster->GetGUID() == victim->GetGUID()) - { - *handled = true; - uint32 stack = triggeredByAura->GetStackAmount(); - int32 const mod = (GetMap()->GetSpawnMode() & 1) ? 1500 : 1250; - int32 dmg = 0; - for (uint8 i = 1; i <= stack; ++i) - dmg += mod * i; - caster->CastCustomSpell(70701, SPELLVALUE_BASE_POINT0, dmg); - } - break; - } - // Ball of Flames Proc - case 71756: - case 72782: - case 72783: - case 72784: - RemoveAuraFromStack(dummySpell->Id); - *handled = true; - break; - // Discerning Eye of the Beast - case 59915: - { - CastSpell(this, 59914, true); // 59914 already has correct basepoints in DBC, no need for custom bp - *handled = true; - break; - } - // Swift Hand of Justice - case 59906: - { - int32 bp0 = CalculatePct(GetMaxHealth(), dummySpell->Effects[EFFECT_0]. CalcValue()); - CastCustomSpell(this, 59913, &bp0, nullptr, nullptr, true); - *handled = true; - break; - } - } - - break; - case SPELLFAMILY_MAGE: - { - // Combustion - switch (dummySpell->Id) - { - case 11129: - { - *handled = true; - Unit* caster = triggeredByAura->GetCaster(); - if (!caster || !damage) - return false; - - // last charge and crit - if (triggeredByAura->GetCharges() <= 1 && (procEx & PROC_EX_CRITICAL_HIT)) - return true; // charge counting (will removed) - - CastSpell(this, 28682, true); - - return procEx & PROC_EX_CRITICAL_HIT; - } - // Empowered Fire - case 31656: - case 31657: - case 31658: - { - *handled = true; - - SpellInfo const* spInfo = sSpellMgr->GetSpellInfo(67545); - if (!spInfo) - return false; - - int32 bp0 = int32(CalculatePct(GetMaxPower(POWER_MANA), spInfo->Effects[0].CalcValue())); - CastCustomSpell(this, 67545, &bp0, nullptr, nullptr, true, nullptr, triggeredByAura->GetEffect(EFFECT_0), GetGUID()); - return true; - } - } - break; - } - case SPELLFAMILY_DEATHKNIGHT: - { - // Blood of the North - // Reaping - // Death Rune Mastery - // xinef: Icon 22 is used for item bonus, skip - if (dummySpell->SpellIconID == 3041 || (dummySpell->SpellIconID == 22 && dummySpell->Id != 62459) || dummySpell->SpellIconID == 2622) - { - *handled = true; - // Convert recently used Blood Rune to Death Rune - if (Player* player = ToPlayer()) - { - if (!player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY)) - return false; - - // xinef: not true - //RuneType rune = ToPlayer()->GetLastUsedRune(); - // can't proc from death rune use - //if (rune == RUNE_DEATH) - // return false; - AuraEffect* aurEff = triggeredByAura->GetEffect(EFFECT_0); - if (!aurEff) - return false; - - // Reset amplitude - set death rune remove timer to 30s - aurEff->ResetPeriodic(true); - uint32 runesLeft; - - if (dummySpell->SpellIconID == 2622) - runesLeft = 2; - else - runesLeft = 1; - - for (uint8 i = 0; i < MAX_RUNES && runesLeft; ++i) - { - if (dummySpell->SpellIconID == 2622) - { - if (player->GetCurrentRune(i) == RUNE_DEATH || - player->GetBaseRune(i) == RUNE_BLOOD) - continue; - } - else - { - if (player->GetCurrentRune(i) == RUNE_DEATH || - player->GetBaseRune(i) != RUNE_BLOOD) - continue; - } - if (player->GetRuneCooldown(i) != player->GetRuneBaseCooldown(i, false)) - continue; - - --runesLeft; - // Mark aura as used - player->AddRuneByAuraEffect(i, RUNE_DEATH, aurEff); - } - return true; - } - return false; - } - break; - } - case SPELLFAMILY_WARRIOR: - { - switch (dummySpell->Id) - { - // Item - Warrior T10 Protection 4P Bonus - case 70844: - { - int32 basepoints0 = CalculatePct(GetMaxHealth(), dummySpell->Effects[EFFECT_1]. CalcValue()); - CastCustomSpell(this, 70845, &basepoints0, nullptr, nullptr, true); - break; - } - default: - break; - } - break; - } - case SPELLFAMILY_SHAMAN: - { - // Flurry - if ((dummySpell->SpellFamilyFlags[1] & 0x00000200) != 0) - { - if (cooldown) - { - if (HasSpellCooldown(dummySpell->Id)) - { - *handled = true; - break; - } - - AddSpellCooldown(dummySpell->Id, 0, cooldown); - } - } - break; - } - } - return false; -} - -bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlags, uint32 procEx, uint32 cooldown, uint32 procPhase, ProcEventInfo& eventInfo) -{ - // Get triggered aura spell info - SpellInfo const* auraSpellInfo = triggeredByAura->GetSpellInfo(); - - // Basepoints of trigger aura - int32 triggerAmount = triggeredByAura->GetAmount(); - - // Set trigger spell id, target, custom basepoints - uint32 trigger_spell_id = auraSpellInfo->Effects[triggeredByAura->GetEffIndex()].TriggerSpell; - - Unit* target = nullptr; - int32 basepoints0 = 0; - - if (triggeredByAura->GetAuraType() == SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE) - basepoints0 = triggerAmount; - - Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() - ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; - - // Try handle unknown trigger spells - //if (sSpellMgr->GetSpellInfo(trigger_spell_id) == nullptr) - { - switch (auraSpellInfo->SpellFamilyName) - { - case SPELLFAMILY_GENERIC: - switch (auraSpellInfo->Id) - { - case 43820: // Charm of the Witch Doctor (Amani Charm of the Witch Doctor trinket) - // Pct value stored in dummy - if (!victim) - return false; - basepoints0 = victim->GetCreateHealth() * auraSpellInfo->Effects[1].CalcValue() / 100; - target = victim; - break; - case 57345: // Darkmoon Card: Greatness - { - float stat = 0.0f; - // strength - if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 60229; stat = GetStat(STAT_STRENGTH); } - // agility - if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 60233; stat = GetStat(STAT_AGILITY); } - // intellect - if (GetStat(STAT_INTELLECT) > stat) { trigger_spell_id = 60234; stat = GetStat(STAT_INTELLECT);} - // spirit - if (GetStat(STAT_SPIRIT) > stat) { trigger_spell_id = 60235; } - break; - } - case 67702: // Death's Choice, Item - Coliseum 25 Normal Melee Trinket - { - if (!damage) - return false; - float stat = 0.0f; - // strength - if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 67708; stat = GetStat(STAT_STRENGTH); } - // agility - if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 67703; } - break; - } - case 67771: // Death's Choice (heroic), Item - Coliseum 25 Heroic Melee Trinket - { - if (!damage) - return false; - float stat = 0.0f; - // strength - if (GetStat(STAT_STRENGTH) > stat) { trigger_spell_id = 67773; stat = GetStat(STAT_STRENGTH); } - // agility - if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 67772; } - break; - } - case 27522: // Mana Drain Trigger - case 40336: // Mana Drain Trigger - case 46939: // Black Bow of the Betrayer - { - // On successful melee or ranged attack gain $29471s1 mana and if possible drain $27526s1 mana from the target. - if (IsAlive()) - CastSpell(this, 29471, true, castItem, triggeredByAura); - if (victim && victim->IsAlive()) - CastSpell(victim, 27526, true, castItem, triggeredByAura); - return true; - } - // Forge of Souls, Devourer of Souls, Mirrored Soul - case 69023: - { - int32 dmg = damage * 0.45f; - if (dmg > 0) - if (Aura* a = GetAura(69023)) - if (Unit* c = a->GetCaster()) - CastCustomSpell(c, 69034, &dmg, 0, 0, true); - return true; - } - // Soul-Trader Beacon proc aura - case 50051: - { - if (!victim) - return false; - - if (Creature* cr = GetCompanionPet()) - cr->CastSpell(victim, 50101, true); - - return false; - } - } - break; - case SPELLFAMILY_MAGE: - if (auraSpellInfo->SpellIconID == 2127) // Blazing Speed - { - switch (auraSpellInfo->Id) - { - case 31641: // Rank 1 - case 31642: // Rank 2 - trigger_spell_id = 31643; - break; - default: - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} miss posibly Blazing Speed", auraSpellInfo->Id); - return false; - } - } - else if (auraSpellInfo->Id == 71761) // Deep Freeze Immunity State (only permanent) - { - Creature* creature = victim->ToCreature(); - if (!creature || !creature->HasMechanicTemplateImmunity(1 << (MECHANIC_STUN - 1))) - return false; - } - break; - case SPELLFAMILY_WARLOCK: - { - // Nether Protection - if (auraSpellInfo->SpellIconID == 1985) - { - if (!procSpell) - return false; - switch (GetFirstSchoolInMask(procSpell->GetSchoolMask())) - { - case SPELL_SCHOOL_NORMAL: - return false; // ignore - case SPELL_SCHOOL_HOLY: - trigger_spell_id = 54370; - break; - case SPELL_SCHOOL_FIRE: - trigger_spell_id = 54371; - break; - case SPELL_SCHOOL_NATURE: - trigger_spell_id = 54375; - break; - case SPELL_SCHOOL_FROST: - trigger_spell_id = 54372; - break; - case SPELL_SCHOOL_SHADOW: - trigger_spell_id = 54374; - break; - case SPELL_SCHOOL_ARCANE: - trigger_spell_id = 54373; - break; - default: - return false; - } - } - break; - } - case SPELLFAMILY_PRIEST: - { - // Blessed Recovery - if (auraSpellInfo->SpellIconID == 1875) - { - switch (auraSpellInfo->Id) - { - case 27811: - trigger_spell_id = 27813; - break; - case 27815: - trigger_spell_id = 27817; - break; - case 27816: - trigger_spell_id = 27818; - break; - default: - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} not handled in BR", auraSpellInfo->Id); - return false; - } - basepoints0 = CalculatePct(int32(damage), triggerAmount) / 3; - target = this; - // Add remaining ticks to healing done - CastDelayedSpellWithPeriodicAmount(this, trigger_spell_id, SPELL_AURA_PERIODIC_HEAL, basepoints0); - return true; - } - break; - } - case SPELLFAMILY_DRUID: - { - switch (auraSpellInfo->Id) - { - // Druid Forms Trinket - case 37336: - { - switch (GetShapeshiftForm()) - { - case FORM_NONE: - trigger_spell_id = 37344; - break; - case FORM_CAT: - trigger_spell_id = 37341; - break; - case FORM_BEAR: - case FORM_DIREBEAR: - trigger_spell_id = 37340; - break; - case FORM_TREE: - trigger_spell_id = 37342; - break; - case FORM_MOONKIN: - trigger_spell_id = 37343; - break; - default: - return false; - } - break; - } - // Druid T9 Feral Relic (Lacerate, Swipe, Mangle, and Shred) - case 67353: - { - switch (GetShapeshiftForm()) - { - case FORM_CAT: - trigger_spell_id = 67355; - break; - case FORM_BEAR: - case FORM_DIREBEAR: - trigger_spell_id = 67354; - break; - default: - return false; - } - break; - } - default: - break; - } - break; - } - case SPELLFAMILY_HUNTER: - { - if (auraSpellInfo->SpellIconID == 3247) // Piercing Shots - { - if (!victim) - return false; - - switch (auraSpellInfo->Id) - { - case 53234: // Rank 1 - case 53237: // Rank 2 - case 53238: // Rank 3 - trigger_spell_id = 63468; - break; - default: - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} miss posibly Piercing Shots", auraSpellInfo->Id); - return false; - } - SpellInfo const* TriggerPS = sSpellMgr->GetSpellInfo(trigger_spell_id); - if (!TriggerPS) - return false; - - basepoints0 = CalculatePct(int32(damage), triggerAmount) / (TriggerPS->GetMaxDuration() / TriggerPS->Effects[0].Amplitude); - victim->CastDelayedSpellWithPeriodicAmount(this, trigger_spell_id, SPELL_AURA_PERIODIC_DAMAGE, basepoints0); - return true; - } - // Item - Hunter T9 4P Bonus (Steady Shot) - else if (auraSpellInfo->Id == 67151) - { - if (!IsPlayer() || !ToPlayer()->GetPet()) - return false; - - target = ToPlayer()->GetPet(); - trigger_spell_id = 68130; - break; - } - break; - } - case SPELLFAMILY_PALADIN: - { - switch (auraSpellInfo->Id) - { - case 37657: // Lightning Capacitor - case 54841: // Thunder Capacitor - case 67712: // Item - Coliseum 25 Normal Caster Trinket - case 67758: // Item - Coliseum 25 Heroic Caster Trinket - { - if (!victim || !victim->IsAlive() || !IsPlayer()) - return false; - - uint32 stack_spell_id = 0; - switch (auraSpellInfo->Id) - { - case 37657: - stack_spell_id = 37658; - trigger_spell_id = 37661; - break; - case 54841: - stack_spell_id = 54842; - trigger_spell_id = 54843; - break; - case 67712: - stack_spell_id = 67713; - trigger_spell_id = 67714; - break; - case 67758: - stack_spell_id = 67759; - trigger_spell_id = 67760; - break; - } - - if (cooldown && ToPlayer()->HasSpellCooldown(stack_spell_id)) - { - return false; - } - - CastSpell(this, stack_spell_id, true, nullptr, triggeredByAura); - - Aura* dummy = GetAura(stack_spell_id); - if (!dummy || dummy->GetStackAmount() < triggerAmount) - { - return false; - } - - if (cooldown) - { - ToPlayer()->AddSpellCooldown(stack_spell_id, 0, cooldown); - } - RemoveAurasDueToSpell(stack_spell_id); - CastSpell(victim, trigger_spell_id, true, nullptr, triggeredByAura); - return true; - } - default: - // Illumination - if (auraSpellInfo->SpellIconID == 241) - { - if (!procSpell) - return false; - // procspell is triggered spell but we need mana cost of original casted spell - uint32 originalSpellId = procSpell->Id; - // Holy Shock heal - if (procSpell->SpellFamilyFlags[1] & 0x00010000) - { - switch (procSpell->Id) - { - case 25914: - originalSpellId = 20473; - break; - case 25913: - originalSpellId = 20929; - break; - case 25903: - originalSpellId = 20930; - break; - case 27175: - originalSpellId = 27174; - break; - case 33074: - originalSpellId = 33072; - break; - case 48820: - originalSpellId = 48824; - break; - case 48821: - originalSpellId = 48825; - break; - default: - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} not handled in HShock", procSpell->Id); - return false; - } - } - SpellInfo const* originalSpell = sSpellMgr->GetSpellInfo(originalSpellId); - if (!originalSpell) - { - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} unknown but selected as original in Illu", originalSpellId); - return false; - } - // percent stored in effect 1 (class scripts) base points - int32 cost = int32(originalSpell->ManaCost + CalculatePct(GetCreateMana(), originalSpell->ManaCostPercentage)); - basepoints0 = CalculatePct(cost, auraSpellInfo->Effects[1].CalcValue()); - trigger_spell_id = 20272; - target = this; - } - break; - } - break; - } - case SPELLFAMILY_SHAMAN: - { - // Lightning Shield (overwrite non existing triggered spell call in spell.dbc - if (auraSpellInfo->SpellFamilyFlags[0] & 0x400 && auraSpellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT)) - { - // Do not proc off from self-casted items - if (Spell const* spell = eventInfo.GetProcSpell()) - { - if (spell->m_castItemGUID && victim->GetGUID() == GetGUID()) - { - return false; - } - } - - trigger_spell_id = sSpellMgr->GetSpellWithRank(26364, auraSpellInfo->GetRank()); - } - // Nature's Guardian - else if (auraSpellInfo->SpellIconID == 2013) - { - // Check health condition - should drop to less 30% (damage deal after this!) - if (!HealthBelowPctDamaged(30, damage)) - return false; - - if (victim && victim->IsAlive()) - victim->GetThreatMgr().ModifyThreatByPercent(this, -10); - - basepoints0 = int32(CountPctFromMaxHealth(triggerAmount)); - trigger_spell_id = 31616; - target = this; - } - break; - } - case SPELLFAMILY_DEATHKNIGHT: - { - // Acclimation - if (auraSpellInfo->SpellIconID == 1930) - { - if (!procSpell) - return false; - switch (GetFirstSchoolInMask(procSpell->GetSchoolMask())) - { - case SPELL_SCHOOL_NORMAL: - return false; // ignore - case SPELL_SCHOOL_HOLY: - trigger_spell_id = 50490; - break; - case SPELL_SCHOOL_FIRE: - trigger_spell_id = 50362; - break; - case SPELL_SCHOOL_NATURE: - trigger_spell_id = 50488; - break; - case SPELL_SCHOOL_FROST: - trigger_spell_id = 50485; - break; - case SPELL_SCHOOL_SHADOW: - trigger_spell_id = 50489; - break; - case SPELL_SCHOOL_ARCANE: - trigger_spell_id = 50486; - break; - default: - return false; - } - } - // Blood Presence (Improved) - else if (auraSpellInfo->Id == 63611) - { - if (!IsPlayer()) - return false; - - trigger_spell_id = 50475; - basepoints0 = CalculatePct(int32(damage), triggerAmount); - } - break; - } - } - } - - // All ok. Check current trigger spell - SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(trigger_spell_id); - if (!triggerEntry) - { - // Don't cast unknown spell - LOG_ERROR("entities.unit", "Unit::HandleProcTriggerSpell: Spell {} (effIndex: {}) has unknown TriggerSpell {}. Unhandled custom case?", auraSpellInfo->Id, triggeredByAura->GetEffIndex(), trigger_spell_id); - return false; - } - - // not allow proc extra attack spell at extra attack - if (triggerEntry->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) - { - uint32 lastExtraAttackSpell = eventInfo.GetActor()->GetLastExtraAttackSpell(); - - // Patch 1.12.0(?) extra attack abilities can no longer chain proc themselves - if (lastExtraAttackSpell == trigger_spell_id) - { - return false; - } - - // Patch 2.2.0 Sword Specialization (Warrior, Rogue) extra attack can no longer proc additional extra attacks - // 3.3.5 Sword Specialization (Warrior), Hack and Slash (Rogue) - if (lastExtraAttackSpell == SPELL_SWORD_SPECIALIZATION || lastExtraAttackSpell == SPELL_HACK_AND_SLASH) - { - return false; - } - } - - // Custom requirements (not listed in procEx) Warning! damage dealing after this - // Custom triggered spells - switch (auraSpellInfo->Id) - { - // Deep Wounds - case 12834: - case 12849: - case 12867: - { - if (!IsPlayer()) - return false; - - if (procFlags & PROC_FLAG_DONE_OFFHAND_ATTACK) - basepoints0 = int32((GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE) + GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE)) / 2.0f); - else - basepoints0 = int32((GetFloatValue(UNIT_FIELD_MAXDAMAGE) + GetFloatValue(UNIT_FIELD_MINDAMAGE)) / 2.0f); - break; - } - // Persistent Shield (Scarab Brooch trinket) - // This spell originally trigger 13567 - Dummy Trigger (vs dummy efect) - case 26467: - { - basepoints0 = int32(CalculatePct(damage, 15)); - target = victim; - trigger_spell_id = 26470; - break; - } - // Unyielding Knights (item exploit 29108\29109) - case 38164: - { - if (!victim || victim->GetEntry() != 19457) // Proc only if your target is Grillok - return false; - break; - } - // Deflection - case 52420: - { - if (!HealthBelowPct(35)) - return false; - break; - } - - // Cheat Death - case 28845: - { - // When your health drops below 20% - if (HealthBelowPctDamaged(20, damage) || HealthBelowPct(20)) - return false; - break; - } - // Deadly Swiftness (Rank 1) - case 31255: - { - // whenever you deal damage to a target who is below 20% health. - if (!victim || !victim->IsAlive() || victim->HealthAbovePct(20)) - return false; - - target = this; - trigger_spell_id = 22588; - [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. - } - // Bonus Healing (Crystal Spire of Karabor mace) - case 40971: - { - // If your target is below $s1% health - if (!victim || !victim->IsAlive() || victim->HealthAbovePct(triggerAmount)) - return false; - break; - } - // Rapid Recuperation - case 53228: - case 53232: - { - // This effect only from Rapid Fire (ability cast) - if (!procSpell || !(procSpell->SpellFamilyFlags[0] & 0x20)) - return false; - break; - } - // Decimation - case 63156: - case 63158: - // Can proc only if target has hp below 35% - if (!victim || !victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, procSpell, this)) - return false; - break; - // Ulduar, Hodir, Toasty Fire - case 62821: - if (!this->IsPlayer()) // spell has Attribute, but persistent area auras ignore it - return false; - break; - case 15337: // Improved Spirit Tap (Rank 1) - case 15338: // Improved Spirit Tap (Rank 2) - { - if (!procSpell) - return false; - - if (procSpell->SpellFamilyFlags[0] & 0x800000) - if ((procSpell->Id != 58381) || !roll_chance_i(50)) - return false; - - target = victim; - break; - } - // Professor Putricide - Ooze Spell Tank Protection - case 71770: - if (victim) - victim->CastSpell(victim, trigger_spell_id, true); // EffectImplicitTarget is self - return true; - case 45057: // Evasive Maneuvers (Commendation of Kael`thas trinket) - case 71634: // Item - Icecrown 25 Normal Tank Trinket 1 - case 71640: // Item - Icecrown 25 Heroic Tank Trinket 1 - case 75475: // Item - Chamber of Aspects 25 Normal Tank Trinket - case 75481: // Item - Chamber of Aspects 25 Heroic Tank Trinket - { - // Procs only if damage takes health below $s1% - if (!HealthBelowPctDamaged(triggerAmount, damage)) - return false; - break; - } - default: - break; - } - - if (auraSpellInfo->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT) - { - // Xinef: keep this order, Aura 70656 has SpellIconID 85! - // Item - Death Knight T10 Melee 4P Bonus - if (auraSpellInfo->Id == 70656) - { - if (!IsPlayer() || !IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY)) - return false; - - for (uint8 i = 0; i < MAX_RUNES; ++i) - if (ToPlayer()->GetRuneCooldown(i) == 0) - return false; - } - // Blade Barrier - else if (auraSpellInfo->SpellIconID == 85) - { - Player* plr = ToPlayer(); - if (!plr || !plr->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_ABILITY) || !procSpell) - return false; - - if (!plr->IsBaseRuneSlotsOnCooldown(RUNE_BLOOD)) - return false; - } - // Rime - else if (auraSpellInfo->SpellIconID == 56) - { - if (!IsPlayer()) - return false; - - // Howling Blast - ToPlayer()->RemoveCategoryCooldown(1248); - } - } - - // Custom basepoints/target for exist spell - // dummy basepoints or other customs - switch (trigger_spell_id) - { - // Auras which should proc on area aura source (caster in this case): - // Turn the Tables - case 52914: - case 52915: - case 52910: - // Honor Among Thieves - case 51699: - { - target = triggeredByAura->GetBase()->GetCaster(); - if (!target) - return false; - - if (Player* pTarget = target->ToPlayer()) - { - if (cooldown) - { - if (pTarget->HasSpellCooldown(trigger_spell_id)) - return false; - pTarget->AddSpellCooldown(trigger_spell_id, 0, cooldown); - } - - Unit* cptarget = nullptr; - if (trigger_spell_id == 51699) - { - cptarget = pTarget->GetComboTarget(); - if (!cptarget) - { - cptarget = pTarget->GetSelectedUnit(); - } - } - else - cptarget = target; - - if (cptarget) - { - target->CastSpell(cptarget, trigger_spell_id, true); - return true; - } - } - return false; - } - // Cast positive spell on enemy target - case 20233: // Improved Lay on Hands (cast on target) - { - target = victim; - break; - } - // Ruby Drake, Evasive Aura - case 50241: - { - if (GetAura(50240)) - return false; - - break; - } - // Combo points add triggers (need add combopoint only for main target, and after possible combopoints reset) - case 15250: // Rogue Setup - { - // applied only for main target - if (!victim || (IsPlayer() && victim != ToPlayer()->GetSelectedUnit())) - return false; - break; // continue normal case - } - // Finish movies that add combo - case 14189: // Seal Fate (Netherblade set) - case 14157: // Ruthlessness - { - victim = nullptr; - // Need add combopoint AFTER finish movie (or they dropped in finish phase) - break; - } - // Item - Druid T10 Balance 2P Bonus - case 16870: - { - if (HasAura(70718)) - CastSpell(this, 70721, true); - RemoveAurasDueToSpell(trigger_spell_id); - break; - } - // Shamanistic Rage triggered spell - case 30824: - { - basepoints0 = int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), triggerAmount)); - break; - } - // Enlightenment (trigger only from mana cost spells) - case 35095: - { - if (!procSpell || procSpell->PowerType != POWER_MANA || (procSpell->ManaCost == 0 && procSpell->ManaCostPercentage == 0 && procSpell->ManaCostPerlevel == 0)) - return false; - break; - } - case 46916: // Slam! (Bloodsurge proc) - case 52437: // Sudden Death - { - // Item - Warrior T10 Melee 4P Bonus - if (AuraEffect const* aurEff = GetAuraEffect(70847, 0)) - { - if (!roll_chance_i(aurEff->GetAmount())) - { - // Xinef: dont allow normal proc to override set one - if (GetAura((trigger_spell_id == 46916) ? 71072 : 71069)) - return false; - // Xinef: just to be sure - RemoveAurasDueToSpell(70849); - break; - } - - // Xinef: fully remove all auras and reapply once more - RemoveAurasDueToSpell(70849); - RemoveAurasDueToSpell(71072); - RemoveAurasDueToSpell(71069); - - CastSpell(this, 70849, true, castItem, triggeredByAura); // Extra Charge! - if (trigger_spell_id == 46916) - CastSpell(this, 71072, true, castItem, triggeredByAura); // Slam GCD Reduced - else - CastSpell(this, 71069, true, castItem, triggeredByAura); // Execute GCD Reduced - } - break; - } - // Sword and Board - case 50227: - { - // Remove cooldown on Shield Slam - if (IsPlayer()) - ToPlayer()->RemoveCategoryCooldown(1209); - break; - } - // Maelstrom Weapon - case 53817: - { - // have rank dependent proc chance, ignore too often cases - // PPM = 2.5 * (rank of talent), - uint32 rank = auraSpellInfo->GetRank(); - // 5 rank -> 100% 4 rank -> 80% and etc from full rate - if (!roll_chance_i(20 * rank)) - return false; - - // Item - Shaman T10 Enhancement 4P Bonus - if (AuraEffect const* aurEff = GetAuraEffect(70832, 0)) - if (Aura const* maelstrom = GetAura(53817)) - // xinef: we have 4 charges and all proc conditions are met - aura reaches 5 charges - if ((maelstrom->GetStackAmount() == 4) && roll_chance_i(aurEff->GetAmount())) - CastSpell(this, 70831, true, castItem, triggeredByAura); - - break; - } - // Astral Shift - case 52179: - { - if (!procSpell || !(procEx & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) || this == victim) - return false; - - // Need stun, fear or silence mechanic - if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_SILENCE) | (1 << MECHANIC_STUN) | (1 << MECHANIC_FEAR)))) - return false; - break; - } - // Glyph of Death's Embrace - case 58679: - { - // Proc only from healing part of Death Coil. Check is essential as all Death Coil spells have 0x2000 mask in SpellFamilyFlags - if (!procSpell || !(procSpell->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && procSpell->SpellFamilyFlags[0] == 0x80002000)) - return false; - break; - } - // Glyph of Death Grip - case 58628: - { - // remove cooldown of Death Grip - if (IsPlayer()) - ToPlayer()->RemoveSpellCooldown(49576, true); - return true; - } - // Savage Defense - case 62606: - { - basepoints0 = CalculatePct(triggerAmount, GetTotalAttackPowerValue(BASE_ATTACK)); - break; - } - // Body and Soul - case 64128: - case 65081: - { - // Proc only from PW:S cast - if (!procSpell || !(procSpell->SpellFamilyFlags[0] & 0x00000001)) - return false; - break; - } - // Culling the Herd - case 70893: - { - if (!procSpell) - { - return false; - } - // check if we're doing a critical hit - if (!(procSpell->SpellFamilyFlags[1] & 0x10000000) && (procEx != PROC_EX_CRITICAL_HIT)) - return false; - // check if we're procced by Claw, Bite or Smack (need to use the spell icon ID to detect it) - if (!(procSpell->SpellIconID == 262 || procSpell->SpellIconID == 1680 || procSpell->SpellIconID == 473)) - return false; - break; - } - // Fingers of Frost, synchronise with Frostbite - case 44544: - { - if (procPhase == PROC_SPELL_PHASE_HIT) - { - // Find Frostbite - if (AuraEffect* aurEff = this->GetAuraEffect(SPELL_AURA_ADD_TARGET_TRIGGER, SPELLFAMILY_MAGE, 119, EFFECT_0)) - { - if (!victim) - return false; - - uint8 fofRank = sSpellMgr->GetSpellRank(triggeredByAura->GetId()); - uint8 fbRank = sSpellMgr->GetSpellRank(aurEff->GetId()); - uint8 chance = uint8(std::ceil(fofRank * fbRank * 16.6f)); - - if (roll_chance_i(chance)) - CastSpell(victim, aurEff->GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, true); - } - } - break; - } - } - - // try detect target manually if not set - if (!target) - target = !(procFlags & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS)) && triggerEntry->IsPositive() ? this : victim; - - if (cooldown) - { - if (HasSpellCooldown(triggerEntry->Id)) - return false; - - AddSpellCooldown(triggerEntry->Id, 0, cooldown); - } - - if (basepoints0) - CastCustomSpell(target, triggerEntry->Id, &basepoints0, nullptr, nullptr, true, castItem, triggeredByAura); - else - CastSpell(target, triggerEntry->Id, true, castItem, triggeredByAura); - - return true; -} - -bool Unit::HandleOverrideClassScriptAuraProc(Unit* victim, uint32 /*damage*/, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 cooldown) -{ - int32 scriptId = triggeredByAura->GetMiscValue(); - - if (!victim || !victim->IsAlive()) - return false; - - Item* castItem = triggeredByAura->GetBase()->GetCastItemGUID() && IsPlayer() - ? ToPlayer()->GetItemByGuid(triggeredByAura->GetBase()->GetCastItemGUID()) : nullptr; - - uint32 triggered_spell_id = 0; - - switch (scriptId) - { - case 836: // Improved Blizzard (Rank 1) - { - if (!procSpell || procSpell->SpellVisual[0] != 9487) - return false; - triggered_spell_id = 12484; - break; - } - case 988: // Improved Blizzard (Rank 2) - { - if (!procSpell || procSpell->SpellVisual[0] != 9487) - return false; - triggered_spell_id = 12485; - break; - } - case 989: // Improved Blizzard (Rank 3) - { - if (!procSpell || procSpell->SpellVisual[0] != 9487) - return false; - triggered_spell_id = 12486; - break; - } - case 4533: // Dreamwalker Raiment 2 pieces bonus - { - // Chance 50% - if (!roll_chance_i(50)) - return false; - - switch (victim->getPowerType()) - { - case POWER_MANA: - triggered_spell_id = 28722; - break; - case POWER_RAGE: - triggered_spell_id = 28723; - break; - case POWER_ENERGY: - triggered_spell_id = 28724; - break; - default: - return false; - } - break; - } - case 4537: // Dreamwalker Raiment 6 pieces bonus - triggered_spell_id = 28750; // Blessing of the Claw - break; - case 5497: // Improved Mana Gems - triggered_spell_id = 37445; // Mana Surge - break; - case 7010: // Revitalize - can proc on full hp target - case 7011: - case 7012: - { - if (!roll_chance_i(triggeredByAura->GetAmount())) - return false; - switch (victim->getPowerType()) - { - case POWER_MANA: - triggered_spell_id = 48542; - break; - case POWER_RAGE: - triggered_spell_id = 48541; - break; - case POWER_ENERGY: - triggered_spell_id = 48540; - break; - case POWER_RUNIC_POWER: - triggered_spell_id = 48543; - break; - default: - break; - } - break; - } - default: - break; - } - - // not processed - if (!triggered_spell_id) - return false; - - // standard non-dummy case - SpellInfo const* triggerEntry = sSpellMgr->GetSpellInfo(triggered_spell_id); - - if (!triggerEntry) - { - LOG_ERROR("entities.unit", "Unit::HandleOverrideClassScriptAuraProc: Spell {} triggering for class script id {}", triggered_spell_id, scriptId); - return false; - } - - if (cooldown) - { - if (HasSpellCooldown(triggered_spell_id)) - return false; - - AddSpellCooldown(triggered_spell_id, 0, cooldown); - } - - CastSpell(victim, triggered_spell_id, true, castItem, triggeredByAura); - - return true; -} - void Unit::setPowerType(Powers new_powertype) { SetByteValue(UNIT_FIELD_BYTES_0, 3, new_powertype); @@ -11896,15 +8675,6 @@ uint32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, uin { case SPELLFAMILY_DRUID: { - // Insect Swarm vs Item - Druid T8 Balance Relic - if (spellProto->SpellFamilyFlags[0] & 0x00200000) - { - if (AuraEffect const* relicAurEff = GetAuraEffect(64950, EFFECT_0)) - { - DoneAdvertisedBenefit += relicAurEff->GetAmount(); - } - } - // Nourish vs Idol of the Flourishing Life if (spellProto->SpellFamilyFlags[1] & 0x02000000) { @@ -12782,26 +9552,22 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u // Taken fixed damage bonus auras int32 TakenAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask()); - // Nourish cast, glyph of nourish + // Nourish cast - 20% bonus if target has Rejuvenation, Regrowth, Lifebloom, or Wild Growth from caster + // Glyph of Nourish is handled by spell_dru_nourish script if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster) { - bool any = false; - bool hasglyph = caster->GetAuraEffectDummy(62971); AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); for (AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i) { - if (((*i)->GetCasterGUID() == caster->GetGUID())) + if ((*i)->GetCasterGUID() == caster->GetGUID()) { SpellInfo const* spell = (*i)->GetSpellInfo(); // Rejuvenation, Regrowth, Lifebloom, or Wild Growth - if (!any && spell->SpellFamilyFlags.HasFlag(0x50, 0x4000010, 0)) + if (spell->SpellFamilyFlags.HasFlag(0x50, 0x4000010, 0)) { TakenTotalMod *= 1.2f; - any = true; + break; } - - if (hasglyph) - TakenTotalMod += 0.06f; } } } @@ -16061,92 +12827,6 @@ bool Unit::isFrozen() const return HasAuraState(AURA_STATE_FROZEN); } -struct ProcTriggeredData -{ - ProcTriggeredData(Aura* _aura) : aura(_aura) - { - effMask = 0; - spellProcEvent = nullptr; - triggerSpelId.fill(0); - } - - SpellProcEventEntry const* spellProcEvent; - Aura* aura; - uint32 effMask; - std::array triggerSpelId; - - bool operator==(const uint32 spellId) const - { - return aura->GetId() == spellId; - } -}; - -typedef std::list< ProcTriggeredData > ProcTriggeredList; - -// List of auras that CAN be trigger but may not exist in spell_proc_event -// in most case need for drop charges -// in some types of aura need do additional check -// for example SPELL_AURA_MECHANIC_IMMUNITY - need check for mechanic -bool InitTriggerAuraData() -{ - for (uint16 i = 0; i < TOTAL_AURAS; ++i) - { - isTriggerAura[i] = false; - isNonTriggerAura[i] = false; - isAlwaysTriggeredAura[i] = false; - } - isTriggerAura[SPELL_AURA_DUMMY] = true; - isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; - isTriggerAura[SPELL_AURA_MOD_THREAT] = true; - isTriggerAura[SPELL_AURA_MOD_STUN] = true; // Aura does not have charges but needs to be removed on trigger - isTriggerAura[SPELL_AURA_MOD_DAMAGE_DONE] = true; - isTriggerAura[SPELL_AURA_MOD_DAMAGE_TAKEN] = true; - isTriggerAura[SPELL_AURA_MOD_RESISTANCE] = true; - isTriggerAura[SPELL_AURA_MOD_STEALTH] = true; - isTriggerAura[SPELL_AURA_MOD_FEAR] = true; // Aura does not have charges but needs to be removed on trigger - isTriggerAura[SPELL_AURA_MOD_ROOT] = true; - isTriggerAura[SPELL_AURA_TRANSFORM] = true; - isTriggerAura[SPELL_AURA_REFLECT_SPELLS] = true; - isTriggerAura[SPELL_AURA_DAMAGE_IMMUNITY] = true; - isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL] = true; - isTriggerAura[SPELL_AURA_PROC_TRIGGER_DAMAGE] = true; - isTriggerAura[SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK] = true; - isTriggerAura[SPELL_AURA_SCHOOL_ABSORB] = true; // Savage Defense untested - isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT] = true; - isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true; - isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true; - isTriggerAura[SPELL_AURA_MECHANIC_IMMUNITY] = true; - isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true; - isTriggerAura[SPELL_AURA_SPELL_MAGNET] = true; - isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true; - isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true; - isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; - isTriggerAura[SPELL_AURA_MOD_MECHANIC_RESISTANCE] = true; - isTriggerAura[SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS] = true; - isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true; - isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true; - isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE] = true; - isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE] = true; - isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true; - isTriggerAura[SPELL_AURA_MOD_DAMAGE_FROM_CASTER] = true; - isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true; - isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true; - - isNonTriggerAura[SPELL_AURA_MOD_POWER_REGEN] = true; - isNonTriggerAura[SPELL_AURA_REDUCE_PUSHBACK] = true; - - isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; - isAlwaysTriggeredAura[SPELL_AURA_MOD_FEAR] = true; - isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT] = true; - isAlwaysTriggeredAura[SPELL_AURA_MOD_STUN] = true; - isAlwaysTriggeredAura[SPELL_AURA_TRANSFORM] = true; - isAlwaysTriggeredAura[SPELL_AURA_SPELL_MAGNET] = true; - isAlwaysTriggeredAura[SPELL_AURA_SCHOOL_ABSORB] = true; - isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true; - - return true; -} - void createProcFlags(SpellInfo const* spellInfo, WeaponAttackType attackType, bool positive, uint32& procAttacker, uint32& procVictim) { if (spellInfo) @@ -16221,67 +12901,7 @@ void createProcFlags(SpellInfo const* spellInfo, WeaponAttackType attackType, bo } } -uint32 createProcExtendMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition) -{ - uint32 procEx = PROC_EX_NONE; - // Check victim state - if (missCondition != SPELL_MISS_NONE) - switch (missCondition) - { - case SPELL_MISS_MISS: - procEx |= PROC_EX_MISS; - break; - case SPELL_MISS_RESIST: - procEx |= PROC_EX_RESIST; - break; - case SPELL_MISS_DODGE: - procEx |= PROC_EX_DODGE; - break; - case SPELL_MISS_PARRY: - procEx |= PROC_EX_PARRY; - break; - case SPELL_MISS_BLOCK: - procEx |= PROC_EX_BLOCK; - break; - case SPELL_MISS_EVADE: - procEx |= PROC_EX_EVADE; - break; - case SPELL_MISS_IMMUNE: - procEx |= PROC_EX_IMMUNE; - break; - case SPELL_MISS_IMMUNE2: - procEx |= PROC_EX_IMMUNE; - break; - case SPELL_MISS_DEFLECT: - procEx |= PROC_EX_DEFLECT; - break; - case SPELL_MISS_ABSORB: - procEx |= PROC_EX_ABSORB; - break; - case SPELL_MISS_REFLECT: - procEx |= PROC_EX_REFLECT; - break; - default: - break; - } - else - { - // On block - if (damageInfo->blocked) - procEx |= PROC_EX_BLOCK; - // On absorb - if (damageInfo->absorb) - procEx |= PROC_EX_ABSORB; - // On crit - if (damageInfo->HitInfo & SPELL_HIT_TYPE_CRIT) - procEx |= PROC_EX_CRITICAL_HIT; - else - procEx |= PROC_EX_NORMAL_HIT; - } - return procEx; -} - -void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, uint32 procExtra, WeaponAttackType attType, SpellInfo const* procSpellInfo, uint32 damage, SpellInfo const* procAura, int8 procAuraEffectIndex, Spell const* procSpell, DamageInfo* damageInfo, HealInfo* healInfo, uint32 procPhase) +void Unit::ProcSkillsAndReactives(bool isVictim, Unit* target, uint32 procFlag, uint32 procExtra, WeaponAttackType attType, SpellInfo const* /*procSpellInfo*/, uint32 /*damage*/, SpellInfo const* /*procAura*/, int8 /*procAuraEffectIndex*/, Spell const* procSpell, DamageInfo* /*damageInfo*/, HealInfo* /*healInfo*/, uint32 procPhase) { // Player is loaded now - do not allow passive spell casts to proc if (IsPlayer() && ToPlayer()->GetSession()->PlayerLoading()) @@ -16367,377 +12987,28 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u } } } - - Unit* actor = isVictim ? target : this; - Unit* actionTarget = !isVictim ? target : this; - - ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, procPhase, procExtra, procSpell, damageInfo, healInfo, procAura, procAuraEffectIndex); - - ProcTriggeredList procTriggered; - // Fill procTriggered list - for (AuraApplicationMap::const_iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) - { - // Do not allow auras to proc from effect triggered by itself - if (procAura && procAura->Id == itr->first) - continue; - - // Xinef: Generic Item Equipment cooldown, -1 is a special marker - if (itr->second->GetBase()->GetCastItemGUID() && HasSpellItemCooldown(itr->first, uint32(-1))) - continue; - - ProcTriggeredData triggerData(itr->second->GetBase()); - // Defensive procs are active on absorbs (so absorption effects are not a hindrance) - bool active = damage || (procExtra & PROC_EX_BLOCK && isVictim); - if (isVictim) - procExtra &= ~PROC_EX_INTERNAL_REQ_FAMILY; - - SpellInfo const* spellProto = itr->second->GetBase()->GetSpellInfo(); - - // only auras that have trigger spell should proc from fully absorbed damage - if (procExtra & PROC_EX_ABSORB && isVictim) - if (damage || spellProto->Effects[EFFECT_0].TriggerSpell || spellProto->Effects[EFFECT_1].TriggerSpell || spellProto->Effects[EFFECT_2].TriggerSpell) - active = true; - - // xinef: fix spell procing from damaging / healing casts if spell has DoT / HoT effect only - // only player spells are taken into account - if (!active && !isVictim && !(procFlag & PROC_FLAG_DONE_PERIODIC) && procSpellInfo && procSpellInfo->SpellFamilyName && (procSpellInfo->HasAura(SPELL_AURA_PERIODIC_DAMAGE) || procSpellInfo->HasAura(SPELL_AURA_PERIODIC_HEAL))) - active = true; - - // AuraScript Hook - if (!triggerData.aura->CallScriptCheckProcHandlers(itr->second, eventInfo)) - { - continue; - } - - bool isTriggeredAtSpellProcEvent = IsTriggeredAtSpellProcEvent(target, triggerData.aura, attType, isVictim, active, triggerData.spellProcEvent, eventInfo); - - // AuraScript Hook - if (!triggerData.aura->CallScriptAfterCheckProcHandlers(itr->second, eventInfo, isTriggeredAtSpellProcEvent)) - { - continue; - } - - // do checks using conditions table - ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_SPELL_PROC, spellProto->Id); - ConditionSourceInfo condInfo = ConditionSourceInfo(eventInfo.GetActor(), eventInfo.GetActionTarget()); - if (!sConditionMgr->IsObjectMeetToConditions(condInfo, conditions)) - { - continue; - } - - bool hasTriggeredProc = false; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - if (itr->second->HasEffect(i)) - { - AuraEffect* aurEff = itr->second->GetBase()->GetEffect(i); - - // Skip this auras - if (isNonTriggerAura[aurEff->GetAuraType()]) - continue; - - // If not trigger by default and spellProcEvent == nullptr - skip - if (!isTriggerAura[aurEff->GetAuraType()] && !triggerData.spellProcEvent) - continue; - - switch (aurEff->GetAuraType()) - { - case SPELL_AURA_PROC_TRIGGER_SPELL: - case SPELL_AURA_MANA_SHIELD: - case SPELL_AURA_DUMMY: - case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: - if (uint32 triggerSpellId = aurEff->GetSpellInfo()->Effects[i].TriggerSpell) - { - triggerData.triggerSpelId[i] = triggerSpellId; - hasTriggeredProc = true; - } - break; - default: - break; - } - - // Some spells must always trigger - //if (isAlwaysTriggeredAura[aurEff->GetAuraType()]) - triggerData.effMask |= 1 << i; - } - } - - if (triggerData.effMask) - { - // If there is aura that triggers another proc aura, make sure that the triggered one is going to be proccessed on top of it - if (hasTriggeredProc) - { - bool proccessed = false; - for (uint8 i = 0; i < EFFECT_ALL; ++i) - { - if (uint32 triggeredSpellId = triggerData.triggerSpelId[i]) - { - auto iter = std::find(procTriggered.begin(), procTriggered.end(), triggeredSpellId); - if (iter != procTriggered.end()) - { - std::advance(iter, 1); - procTriggered.insert(iter, triggerData); - proccessed = true; - break; - } - } - } - - if (!proccessed) - { - procTriggered.push_front(triggerData); - } - } - else - { - procTriggered.push_front(triggerData); - } - } - } - - // Nothing found - if (procTriggered.empty()) - return; - - // Note: must SetCantProc(false) before return - if (procExtra & (PROC_EX_INTERNAL_TRIGGERED | PROC_EX_INTERNAL_CANT_PROC)) - SetCantProc(true); - - // Handle effects proceed this time - for (ProcTriggeredList::const_iterator i = procTriggered.begin(); i != procTriggered.end(); ++i) - { - // look for aura in auras list, it may be removed while proc event processing - if (i->aura->IsRemoved()) - continue; - - bool useCharges = i->aura->IsUsingCharges(); - // no more charges to use, prevent proc - if (useCharges && !i->aura->GetCharges()) - continue; - - bool takeCharges = false; - SpellInfo const* spellInfo = i->aura->GetSpellInfo(); - - AuraApplication* aurApp = i->aura->GetApplicationOfTarget(GetGUID()); - - bool prepare = i->aura->CallScriptPrepareProcHandlers(aurApp, eventInfo); - - // For players set spell cooldown if need - uint32 cooldown = 0; - if (prepare && i->spellProcEvent && i->spellProcEvent->cooldown) - cooldown = i->spellProcEvent->cooldown; - - // Xinef: set cooldown for actual proc - eventInfo.SetProcCooldown(cooldown); - - // Note: must SetCantProc(false) before return - if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS)) - SetCantProc(true); - - bool handled = i->aura->CallScriptProcHandlers(aurApp, eventInfo); - - // "handled" is needed as long as proc can be handled in multiple places - if (!handled && HandleAuraProc(target, damage, i->aura, procSpellInfo, procFlag, procExtra, cooldown, &handled)) - { - uint32 Id = i->aura->GetId(); - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered with value by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), Id); - takeCharges = true; - } - - if (!handled) - for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) - { - if (!(i->effMask & (1 << effIndex))) - continue; - - AuraEffect* triggeredByAura = i->aura->GetEffect(effIndex); - ASSERT(triggeredByAura); - - bool prevented = i->aura->CallScriptEffectProcHandlers(triggeredByAura, aurApp, eventInfo); - if (prevented) - { - takeCharges = true; - continue; - } - - switch (triggeredByAura->GetAuraType()) - { - case SPELL_AURA_PROC_TRIGGER_SPELL: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - // Don`t drop charge or add cooldown for not started trigger - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) - takeCharges = true; - break; - } - case SPELL_AURA_PROC_TRIGGER_DAMAGE: - { - // target has to be valid - if (!eventInfo.GetProcTarget()) - break; - - triggeredByAura->HandleProcTriggerDamageAuraProc(aurApp, eventInfo); // this function is part of the new proc system - takeCharges = true; - break; - } - case SPELL_AURA_MANA_SHIELD: - case SPELL_AURA_DUMMY: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} dummy aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - if (HandleDummyAuraProc(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, eventInfo)) - takeCharges = true; - break; - } - case SPELL_AURA_OBS_MOD_POWER: - case SPELL_AURA_MOD_SPELL_CRIT_CHANCE: - case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN: - case SPELL_AURA_MOD_MELEE_HASTE: - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} aura of spell {})", spellInfo->Id, isVictim ? "a victim's" : "an attacker's", triggeredByAura->GetId()); - takeCharges = true; - break; - case SPELL_AURA_OVERRIDE_CLASS_SCRIPTS: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell id {} (triggered by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - if (HandleOverrideClassScriptAuraProc(target, damage, triggeredByAura, procSpellInfo, cooldown)) - takeCharges = true; - break; - } - case SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting mending (triggered by {} dummy aura of spell {})", - (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - if (damage > 0) - { - HandleAuraRaidProcFromChargeWithValue(triggeredByAura); - takeCharges = true; - } - break; - } - case SPELL_AURA_RAID_PROC_FROM_CHARGE: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting mending (triggered by {} dummy aura of spell {})", - (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - HandleAuraRaidProcFromCharge(triggeredByAura); - takeCharges = true; - break; - } - case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: - { - LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered with value by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) - takeCharges = true; - break; - } - case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: - // Skip melee hits or instant cast spells - // xinef: check channeled spells which are affected by haste also - if (procSpellInfo && (procSpellInfo->SpellFamilyName || !IsPlayer()) && - (procSpellInfo->CalcCastTime() > 0 /*|| - (procSpell->IsChanneled() && procSpell->GetDuration() > 0 && (HasAuraTypeWithAffectMask(SPELL_AURA_PERIODIC_HASTE, procSpell) || procSpell->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC)))*/)) - takeCharges = true; - break; - case SPELL_AURA_REFLECT_SPELLS_SCHOOL: - // Skip Melee hits and spells ws wrong school - if (procSpellInfo && (triggeredByAura->GetMiscValue() & procSpellInfo->SchoolMask)) // School check - takeCharges = true; - break; - case SPELL_AURA_SPELL_MAGNET: - // Skip Melee hits and targets with magnet aura - if (procSpellInfo && (triggeredByAura->GetBase()->GetUnitOwner()->ToUnit() == ToUnit())) // Magnet - takeCharges = true; - break; - case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT: - case SPELL_AURA_MOD_POWER_COST_SCHOOL: - // Skip melee hits and spells ws wrong school or zero cost - if (procSpellInfo && - (procSpellInfo->ManaCost != 0 || procSpellInfo->ManaCostPercentage != 0 || (procSpellInfo->SpellFamilyFlags[1] & 0x2)) && // Cost check, mutilate include - (triggeredByAura->GetMiscValue() & procSpellInfo->SchoolMask)) // School check - takeCharges = true; - break; - case SPELL_AURA_MECHANIC_IMMUNITY: - case SPELL_AURA_MOD_MECHANIC_RESISTANCE: - // Compare mechanic - if (procSpellInfo && procSpellInfo->Mechanic == uint32(triggeredByAura->GetMiscValue())) - takeCharges = true; - break; - case SPELL_AURA_MOD_DAMAGE_FROM_CASTER: - // Compare casters - if (target && triggeredByAura->GetCasterGUID() == target->GetGUID()) - takeCharges = true; - break; - // CC Auras which use their amount amount to drop - // Are there any more auras which need this? - case SPELL_AURA_MOD_CONFUSE: - case SPELL_AURA_MOD_FEAR: - case SPELL_AURA_MOD_STUN: - case SPELL_AURA_MOD_ROOT: - case SPELL_AURA_TRANSFORM: - { - // Spell own direct damage at apply wont break the CC - // Xinef: Or when the aura is at full duration (assume that such auras should be added at the end, skipping all damage procs etc.) - if (procSpellInfo) - if ((!i->aura->IsPermanent() && i->aura->GetDuration() == i->aura->GetMaxDuration()) || procSpellInfo->Id == triggeredByAura->GetId() || - procSpellInfo->HasAttribute(SPELL_ATTR4_REACTIVE_DAMAGE_PROC)) - break; - - // chargeable mods are breaking on hit - if (useCharges) - takeCharges = true; - else if (triggeredByAura->GetAmount()) // aura must have amount - { - int32 damageLeft = triggeredByAura->GetAmount(); - // No damage left - if (damageLeft < int32(damage)) - i->aura->Remove(); - else - triggeredByAura->SetAmount(damageLeft - damage); - } - break; - } - case SPELL_AURA_ABILITY_IGNORE_AURASTATE: - if (procSpellInfo && procSpellInfo->Id == 20647) // hack for warriors execute, both dummy and damage spell are affected by ignore aurastate aura - break; - takeCharges = true; - break; - case SPELL_AURA_ADD_FLAT_MODIFIER: - case SPELL_AURA_ADD_PCT_MODIFIER: - { - if (triggeredByAura->GetSpellModifier()) - { - // Do proc if mod is consumed by spell - if (!procSpell || procSpell->m_appliedMods.find(i->aura) != procSpell->m_appliedMods.end()) - { - takeCharges = true; - } - } - break; - } - default: - takeCharges = true; - break; - } - i->aura->CallScriptAfterEffectProcHandlers(triggeredByAura, aurApp, eventInfo); - } - // Remove charge (aura can be removed by triggers) - // xinef: take into account attribute6 of proc spell - if (prepare && useCharges && takeCharges) - if (!procSpellInfo || isVictim || !procSpellInfo->HasAttribute(SPELL_ATTR6_DO_NOT_CONSUME_RESOURCES)) - i->aura->DropCharge(); - - i->aura->CallScriptAfterProcHandlers(aurApp, eventInfo); - - if (spellInfo->HasAttribute(SPELL_ATTR3_INSTANT_TARGET_PROCS)) - SetCantProc(false); - } - - // Cleanup proc requirements - if (procExtra & (PROC_EX_INTERNAL_TRIGGERED | PROC_EX_INTERNAL_CANT_PROC)) - SetCantProc(false); + // Aura procs are now handled by TriggerAurasProcOnEvent called from ProcSkillsAndAuras } -void Unit::GetProcAurasTriggeredOnEvent(std::list& aurasTriggeringProc, std::list* procAuras, ProcEventInfo eventInfo) +void Unit::GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, std::list* procAuras, ProcEventInfo eventInfo) { + TimePoint now = std::chrono::steady_clock::now(); + + auto processAuraApplication = [&](AuraApplication* aurApp) + { + if (uint8 procEffectMask = aurApp->GetBase()->GetProcEffectMask(aurApp, eventInfo, now)) + { + aurApp->GetBase()->PrepareProcToTrigger(aurApp, eventInfo, now); + aurasTriggeringProc.emplace_back(procEffectMask, aurApp); + } + else + { + if (aurApp->GetBase()->GetSpellInfo()->HasAttribute(SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE)) + if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(aurApp->GetBase()->GetId())) + aurApp->GetBase()->AddProcCooldown(procEntry, now); + } + }; + // use provided list of auras which can proc if (procAuras) { @@ -16745,58 +13016,48 @@ void Unit::GetProcAurasTriggeredOnEvent(std::list& aurasTrigge { ASSERT((*itr)->GetTarget() == this); if (!(*itr)->GetRemoveMode()) - if ((*itr)->GetBase()->IsProcTriggeredOnEvent(*itr, eventInfo)) - { - (*itr)->GetBase()->PrepareProcToTrigger(*itr, eventInfo); - aurasTriggeringProc.push_back(*itr); - } + processAuraApplication(*itr); } } // or generate one on our own else { for (AuraApplicationMap::iterator itr = GetAppliedAuras().begin(); itr != GetAppliedAuras().end(); ++itr) - { - if (itr->second->GetBase()->IsProcTriggeredOnEvent(itr->second, eventInfo)) - { - itr->second->GetBase()->PrepareProcToTrigger(itr->second, eventInfo); - aurasTriggeringProc.push_back(itr->second); - } - } + processAuraApplication(itr->second); } } void Unit::TriggerAurasProcOnEvent(CalcDamageInfo& damageInfo) { DamageInfo dmgInfo = DamageInfo(damageInfo); - TriggerAurasProcOnEvent(nullptr, nullptr, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, 0, 0, damageInfo.procEx, nullptr, &dmgInfo, nullptr); + TriggerAurasProcOnEvent(nullptr, nullptr, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, 0, 0, dmgInfo.GetHitMask(), nullptr, &dmgInfo, nullptr); } void Unit::TriggerAurasProcOnEvent(std::list* myProcAuras, std::list* targetProcAuras, Unit* actionTarget, uint32 typeMaskActor, uint32 typeMaskActionTarget, uint32 spellTypeMask, uint32 spellPhaseMask, uint32 hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) { // prepare data for self trigger ProcEventInfo myProcEventInfo = ProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); - std::list myAurasTriggeringProc; + AuraApplicationProcContainer myAurasTriggeringProc; GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, myProcAuras, myProcEventInfo); // prepare data for target trigger ProcEventInfo targetProcEventInfo = ProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); - std::list targetAurasTriggeringProc; - if (typeMaskActionTarget) - GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo); + AuraApplicationProcContainer targetAurasTriggeringProc; + if (typeMaskActionTarget && actionTarget) + actionTarget->GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo); TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); - if (typeMaskActionTarget) - TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); + if (typeMaskActionTarget && actionTarget) + actionTarget->TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); } -void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, std::list& aurasTriggeringProc) +void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& aurasTriggeringProc) { - for (std::list::iterator itr = aurasTriggeringProc.begin(); itr != aurasTriggeringProc.end(); ++itr) + for (auto const& [procEffectMask, aurApp] : aurasTriggeringProc) { - if (!(*itr)->GetRemoveMode()) - (*itr)->GetBase()->TriggerProcOnEvent(*itr, eventInfo); + if (!aurApp->GetRemoveMode()) + aurApp->GetBase()->TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); } } @@ -17673,159 +13934,6 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) return true; } -bool Unit::IsTriggeredAtSpellProcEvent(Unit* victim, Aura* aura, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry const*& spellProcEvent, ProcEventInfo const& eventInfo) -{ - SpellInfo const* spellProto = aura->GetSpellInfo(); - SpellInfo const* procSpell = eventInfo.GetSpellInfo(); - - // let the aura be handled by new proc system if it has new entry - if (sSpellMgr->GetSpellProcEntry(spellProto->Id)) - return false; - - // Get proc Event Entry - spellProcEvent = sSpellMgr->GetSpellProcEvent(spellProto->Id); - - // Get EventProcFlag - uint32 EventProcFlag; - if (spellProcEvent && spellProcEvent->procFlags) // if exist get custom spellProcEvent->procFlags - EventProcFlag = spellProcEvent->procFlags; - else - EventProcFlag = spellProto->ProcFlags; // else get from spell proto - // Continue if no trigger exist - if (!EventProcFlag) - return false; - - // Additional checks for triggered spells (ignore trap casts) - if (eventInfo.GetHitMask() & PROC_EX_INTERNAL_TRIGGERED && !(EventProcFlag & PROC_FLAG_DONE_TRAP_ACTIVATION)) - { - if (!spellProto->HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS)) - return false; - } - - // Xinef: additional check for player auras - only player spells can trigger player proc auras - // Xinef: skip victim auras - // Excluded player shoot spells - // Excluded player item spells - if (!isVictim && IsPlayer() && !(EventProcFlag & (PROC_FLAG_KILL | PROC_FLAG_DEATH))) - { - if (procSpell && procSpell->SpellFamilyName == SPELLFAMILY_GENERIC && procSpell->GetCategory() != 76 && - (!eventInfo.GetProcSpell() || !eventInfo.GetProcSpell()->m_CastItem) && - (!eventInfo.GetTriggerAuraSpell() || eventInfo.GetTriggerAuraSpell()->SpellFamilyName == SPELLFAMILY_GENERIC)) - { - return false; - } - } - - // Check spellProcEvent data requirements - if (!sSpellMgr->IsSpellProcEventCanTriggeredBy(spellProto, spellProcEvent, EventProcFlag, eventInfo, active)) - return false; - // In most cases req get honor or XP from kill - if (EventProcFlag & PROC_FLAG_KILL && IsPlayer()) - { - bool allow = false; - - if (victim) - allow = ToPlayer()->isHonorOrXPTarget(victim); - - // Shadow Word: Death - can trigger from every kill - if (aura->GetId() == 32409 || aura->GetId() == 18372 || aura->GetId() == 18213) - allow = true; - if (!allow) - return false; - } - // Aura added by spell can`t trigger from self (prevent drop charges/do triggers) - // But except periodic and kill triggers (can triggered from self) - if (procSpell && procSpell->Id == spellProto->Id - && !(spellProto->ProcFlags & (PROC_FLAG_TAKEN_PERIODIC | PROC_FLAG_KILL))) - return false; - - // Check if current equipment allows aura to proc - if (!isVictim && IsPlayer() && !spellProto->HasAttribute(SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT)) - { - Player* player = ToPlayer(); - if (spellProto->EquippedItemClass == ITEM_CLASS_WEAPON) - { - Item* item = nullptr; - if (attType == BASE_ATTACK) - item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); - else if (attType == OFF_ATTACK) - item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); - else - item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); - - if (player->IsInFeralForm()) - return false; - - if (!item || item->IsBroken() || item->GetTemplate()->Class != ITEM_CLASS_WEAPON || !((1 << item->GetTemplate()->SubClass) & spellProto->EquippedItemSubClassMask)) - return false; - } - else if (spellProto->EquippedItemClass == ITEM_CLASS_ARMOR) - { - // Check if player is wearing shield - Item* item = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); - if (!item || item->IsBroken() || item->GetTemplate()->Class != ITEM_CLASS_ARMOR || !((1 << item->GetTemplate()->SubClass) & spellProto->EquippedItemSubClassMask)) - return false; - } - } - // Get chance from spell - float chance = float(spellProto->ProcChance); - // If in spellProcEvent exist custom chance, chance = spellProcEvent->customChance; - if (spellProcEvent && spellProcEvent->customChance) - chance = spellProcEvent->customChance; - // If PPM exist calculate chance from PPM - if (spellProcEvent && spellProcEvent->ppmRate != 0) - { - uint32 attackSpeed = 0; - Unit* attacker = nullptr; - if (!isVictim) - attacker = this; - else if (victim) - attacker = victim; - - if (attacker) - { - if (!procSpell || procSpell->DmgClass == SPELL_DAMAGE_CLASS_MELEE || procSpell->IsRangedWeaponSpell()) - { - attackSpeed = attacker->GetAttackTime(attType); - } - else //spells user their casttime for ppm calculations - { - if (procSpell->CastTimeEntry) - attackSpeed = procSpell->CastTimeEntry->CastTime; - - //instants and fast spells use 1.5s castspeed - if (attackSpeed < 1500) - attackSpeed = 1500; - } - } - chance = GetPPMProcChance(attackSpeed, spellProcEvent->ppmRate, spellProto); - } - - // Custom chances - switch (spellProto->SpellFamilyName) - { - case SPELLFAMILY_WARRIOR: - { - // Recklessness, allow to proc only once for whirlwind - if (spellProto->Id == 1719 && procSpell && procSpell->Id == 44949) - return false; - } - } - - if (eventInfo.GetProcChance()) - { - chance = *eventInfo.GetProcChance(); - } - - // Apply chance modifer aura - if (Player* modOwner = GetSpellModOwner()) - { - modOwner->ApplySpellMod(spellProto->Id, SPELLMOD_CHANCE_OF_SUCCESS, chance); - } - - return roll_chance_f(chance); -} - bool Unit::HandleAuraRaidProcFromChargeWithValue(AuraEffect* triggeredByAura) { // aura can be deleted at casts @@ -18083,17 +14191,17 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp if (killer && (killer->IsPet() || killer->IsTotem())) if (Unit* owner = killer->GetOwner()) { - Unit::ProcDamageAndSpell(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); + Unit::ProcSkillsAndAuras(owner, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); sScriptMgr->OnPlayerCreatureKilledByPet( killer->GetCharmerOrOwnerPlayerOrPlayerItself(), victim->ToCreature()); } if (killer != victim) { - Unit::ProcDamageAndSpell(killer, victim, killer ? PROC_FLAG_KILL : 0, PROC_FLAG_KILLED, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); + Unit::ProcSkillsAndAuras(killer, victim, killer ? PROC_FLAG_KILL : 0, PROC_FLAG_KILLED, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); } // Proc auras on death - must be before aura/combat remove - Unit::ProcDamageAndSpell(victim, nullptr, PROC_FLAG_DEATH, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); + Unit::ProcSkillsAndAuras(victim, nullptr, PROC_FLAG_DEATH, PROC_FLAG_NONE, PROC_EX_NONE, 0, attackType, spellProto, nullptr, -1, spell); // update get killing blow achievements, must be done before setDeathState to be able to require auras on target // and before Spirit of Redemption as it also removes auras diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 1590c2721..96e99ab98 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -347,6 +347,7 @@ private: uint32 m_resist; uint32 m_block; uint32 m_cleanDamage; + uint32 m_hitMask; // amalgamation constructor (used for proc) DamageInfo(DamageInfo const& dmg1, DamageInfo const& dmg2); @@ -355,7 +356,8 @@ public: explicit DamageInfo(Unit* _attacker, Unit* _victim, uint32 _damage, SpellInfo const* _spellInfo, SpellSchoolMask _schoolMask, DamageEffectType _damageType, uint32 cleanDamage = 0); explicit DamageInfo(CalcDamageInfo const& dmgInfo); // amalgamation wrapper DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex); - DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType); + DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, uint32 hitMask); + DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, SpellMissInfo missInfo); void ModifyDamage(int32 amount); void AbsorbDamage(uint32 amount); @@ -373,6 +375,8 @@ public: [[nodiscard]] uint32 GetResist() const { return m_resist; }; [[nodiscard]] uint32 GetBlock() const { return m_block; }; + [[nodiscard]] uint32 GetHitMask() const; + void AddHitMask(uint32 hitMask) { m_hitMask |= hitMask; } [[nodiscard]] uint32 GetUnmitigatedDamage() const; }; @@ -386,9 +390,10 @@ private: uint32 m_absorb; SpellInfo const* const m_spellInfo; SpellSchoolMask const m_schoolMask; + uint32 m_hitMask; public: explicit HealInfo(Unit* _healer, Unit* _target, uint32 _heal, SpellInfo const* _spellInfo, SpellSchoolMask _schoolMask) - : m_healer(_healer), m_target(_target), m_heal(_heal), m_spellInfo(_spellInfo), m_schoolMask(_schoolMask) + : m_healer(_healer), m_target(_target), m_heal(_heal), m_spellInfo(_spellInfo), m_schoolMask(_schoolMask), m_hitMask(0) { m_absorb = 0; m_effectiveHeal = 0; @@ -421,6 +426,8 @@ public: [[nodiscard]] uint32 GetAbsorb() const { return m_absorb; } [[nodiscard]] SpellInfo const* GetSpellInfo() const { return m_spellInfo; }; [[nodiscard]] SpellSchoolMask GetSchoolMask() const { return m_schoolMask; }; + [[nodiscard]] uint32 GetHitMask() const { return m_hitMask; } + void AddHitMask(uint32 hitMask) { m_hitMask |= hitMask; } }; class ProcEventInfo @@ -451,7 +458,7 @@ public: [[nodiscard]] uint32 GetSpellPhaseMask() const { return _spellPhaseMask; } [[nodiscard]] uint32 GetHitMask() const { return _hitMask; } [[nodiscard]] SpellInfo const* GetSpellInfo() const; - [[nodiscard]] SpellSchoolMask GetSchoolMask() const { return SPELL_SCHOOL_MASK_NONE; } + [[nodiscard]] SpellSchoolMask GetSchoolMask() const; [[nodiscard]] Spell const* GetProcSpell() const { return _spell; } [[nodiscard]] DamageInfo* GetDamageInfo() const { return _damageInfo; } [[nodiscard]] HealInfo* GetHealInfo() const { return _healInfo; } @@ -486,7 +493,6 @@ struct CalcDamageInfo WeaponAttackType attackType; // uint32 procAttacker; uint32 procVictim; - uint32 procEx; uint32 cleanDamage; // Used only for rage calculation MeleeHitOutcome hitOutCome; /// @todo: remove this field (need use TargetState) }; @@ -530,7 +536,6 @@ struct SpellPeriodicAuraLogInfo }; void createProcFlags(SpellInfo const* spellInfo, WeaponAttackType attackType, bool positive, uint32& procAttacker, uint32& procVictim); -uint32 createProcExtendMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition); #define MAX_DECLINED_NAME_CASES 5 @@ -627,8 +632,6 @@ typedef std::unordered_map PacketCooldowns; #define ATTACK_DISPLAY_DELAY 200 #define MAX_PLAYER_STEALTH_DETECT_RANGE 30.0f // max distance for detection targets by player -struct SpellProcEventEntry; // used only privately - enum class SpeedOpcodeIndex : uint32 { PC, @@ -671,6 +674,7 @@ public: typedef std::vector AuraEffectList; typedef std::list AuraList; typedef std::list AuraApplicationList; + typedef std::vector> AuraApplicationProcContainer; typedef std::list Diminishing; typedef GuidUnorderedSet ComboPointHolderSet; @@ -1542,14 +1546,14 @@ public: bool CanProc() { return !m_procDeep; } void SetCantProc(bool apply); - static void ProcDamageAndSpell(Unit* actor, Unit* victim, uint32 procAttacker, uint32 procVictim, uint32 procEx, uint32 amount, WeaponAttackType attType = BASE_ATTACK, SpellInfo const* procSpellInfo = nullptr, SpellInfo const* procAura = nullptr, int8 procAuraEffectIndex = -1, Spell const* procSpell = nullptr, DamageInfo* damageInfo = nullptr, HealInfo* healInfo = nullptr, uint32 procPhase = 2 /*PROC_SPELL_PHASE_HIT*/); - void ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, uint32 procExtra, WeaponAttackType attType, SpellInfo const* procSpellInfo, uint32 damage, SpellInfo const* procAura = nullptr, int8 procAuraEffectIndex = -1, Spell const* procSpell = nullptr, DamageInfo* damageInfo = nullptr, HealInfo* healInfo = nullptr, uint32 procPhase = 2 /*PROC_SPELL_PHASE_HIT*/); + static void ProcSkillsAndAuras(Unit* actor, Unit* victim, uint32 procAttacker, uint32 procVictim, uint32 procEx, uint32 amount, WeaponAttackType attType = BASE_ATTACK, SpellInfo const* procSpellInfo = nullptr, SpellInfo const* procAura = nullptr, int8 procAuraEffectIndex = -1, Spell const* procSpell = nullptr, DamageInfo* damageInfo = nullptr, HealInfo* healInfo = nullptr, uint32 procPhase = 2 /*PROC_SPELL_PHASE_HIT*/); + void ProcSkillsAndReactives(bool isVictim, Unit* target, uint32 procFlag, uint32 procExtra, WeaponAttackType attType, SpellInfo const* procSpellInfo, uint32 damage, SpellInfo const* procAura = nullptr, int8 procAuraEffectIndex = -1, Spell const* procSpell = nullptr, DamageInfo* damageInfo = nullptr, HealInfo* healInfo = nullptr, uint32 procPhase = 2 /*PROC_SPELL_PHASE_HIT*/); - void GetProcAurasTriggeredOnEvent(std::list& aurasTriggeringProc, std::list* procAuras, ProcEventInfo eventInfo); + void GetProcAurasTriggeredOnEvent(AuraApplicationProcContainer& aurasTriggeringProc, std::list* procAuras, ProcEventInfo eventInfo); void TriggerAurasProcOnEvent(CalcDamageInfo& damageInfo); void TriggerAurasProcOnEvent(std::list* myProcAuras, std::list* targetProcAuras, Unit* actionTarget, uint32 typeMaskActor, uint32 typeMaskActionTarget, uint32 spellTypeMask, uint32 spellPhaseMask, uint32 hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo); - void TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, std::list& procAuras); + void TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProcContainer& procAuras); [[nodiscard]] float GetWeaponProcChance() const; float GetPPMProcChance(uint32 WeaponSpeed, float PPM, SpellInfo const* spellProto) const; @@ -2188,11 +2192,7 @@ protected: bool _instantCast; private: - bool IsTriggeredAtSpellProcEvent(Unit* victim, Aura* aura, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry const*& spellProcEvent, ProcEventInfo const& eventInfo); - bool HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, ProcEventInfo const& eventInfo); - bool HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, bool* handled); - bool HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, uint32 procPhase, ProcEventInfo& eventInfo); - bool HandleOverrideClassScriptAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 cooldown); + // Legacy proc handlers removed - all procs now use AuraScripts and spell_proc table bool HandleAuraRaidProcFromChargeWithValue(AuraEffect* triggeredByAura); bool HandleAuraRaidProcFromCharge(AuraEffect* triggeredByAura); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index eeff0d3d6..acc3e15d7 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1201,7 +1201,7 @@ void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar) { int32 i = 0; flag96 _mask = 0; - SpellModList const& spellMods = pCurrChar->GetSpellModList(opType); + SpellModContainer const& spellMods = pCurrChar->GetSpellModList(opType); if (spellMods.empty()) continue; diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index f0d54d7e0..7a3b2edf4 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -104,8 +104,8 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS] = &AuraEffect::HandleAuraModSchoolImmunity, // 39 SPELL_AURA_SCHOOL_IMMUNITY &AuraEffect::HandleAuraModDmgImmunity, // 40 SPELL_AURA_DAMAGE_IMMUNITY &AuraEffect::HandleAuraModDispelImmunity, // 41 SPELL_AURA_DISPEL_IMMUNITY - &AuraEffect::HandleNoImmediateEffect, // 42 SPELL_AURA_PROC_TRIGGER_SPELL implemented in Unit::ProcDamageAndSpellFor and Unit::HandleProcTriggerSpell - &AuraEffect::HandleNoImmediateEffect, // 43 SPELL_AURA_PROC_TRIGGER_DAMAGE implemented in Unit::ProcDamageAndSpellFor + &AuraEffect::HandleNoImmediateEffect, // 42 SPELL_AURA_PROC_TRIGGER_SPELL implemented in Aura::TriggerProcOnEvent + &AuraEffect::HandleNoImmediateEffect, // 43 SPELL_AURA_PROC_TRIGGER_DAMAGE implemented in Aura::TriggerProcOnEvent &AuraEffect::HandleAuraTrackCreatures, // 44 SPELL_AURA_TRACK_CREATURES &AuraEffect::HandleAuraTrackResources, // 45 SPELL_AURA_TRACK_RESOURCES &AuraEffect::HandleNULL, // 46 SPELL_AURA_46 (used in test spells 54054 and 54058, and spell 48050) (3.0.8a) @@ -700,8 +700,6 @@ void AuraEffect::CalculateSpellMod() m_spellmod->type = SpellModType(GetAuraType()); // SpellModType value == spell aura types m_spellmod->spellId = GetId(); m_spellmod->mask = GetSpellInfo()->Effects[GetEffIndex()].SpellClassMask; - m_spellmod->charges = GetBase()->GetCharges(); - m_spellmod->priority = GetSpellInfo()->SpellPriority; } m_spellmod->value = GetAmount(); break; @@ -1181,6 +1179,72 @@ void AuraEffect::PeriodicTick(AuraApplication* aurApp, Unit* caster) const } } +bool AuraEffect::CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventInfo) const +{ + bool result = GetBase()->CallScriptCheckEffectProcHandlers(this, aurApp, eventInfo); + if (!result) + return false; + + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + switch (GetAuraType()) + { + case SPELL_AURA_MOD_CONFUSE: + case SPELL_AURA_MOD_FEAR: + case SPELL_AURA_MOD_STUN: + case SPELL_AURA_MOD_ROOT: + case SPELL_AURA_TRANSFORM: + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return false; + + // Spell own damage at apply won't break CC + if (spellInfo && spellInfo == GetSpellInfo()) + { + Aura* aura = GetBase(); + // called from spellcast, should not have ticked yet + if (aura->GetDuration() == aura->GetMaxDuration()) + return false; + } + break; + } + case SPELL_AURA_MECHANIC_IMMUNITY: + case SPELL_AURA_MOD_MECHANIC_RESISTANCE: + // compare mechanic + if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (1 << GetMiscValue()))) + return false; + break; + case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: + // skip melee hits and instant cast spells + if (!eventInfo.GetProcSpell() || !eventInfo.GetProcSpell()->GetCastTime()) + return false; + break; + case SPELL_AURA_MOD_POWER_COST_SCHOOL: + case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT: + { + // Skip melee hits and spells with wrong school or zero cost + if (!spellInfo || !(spellInfo->GetSchoolMask() & GetMiscValue()) + || !spellInfo->ManaCost || !spellInfo->ManaCostPercentage) + return false; + break; + } + case SPELL_AURA_REFLECT_SPELLS_SCHOOL: + // Skip melee hits and spells with wrong school + if (!spellInfo || !(spellInfo->GetSchoolMask() & GetMiscValue())) + return false; + break; + case SPELL_AURA_MOD_DAMAGE_FROM_CASTER: + // Compare casters + if (GetCasterGUID() != eventInfo.GetActor()->GetGUID()) + return false; + break; + default: + break; + } + + return result; +} + void AuraEffect::HandleProc(AuraApplication* aurApp, ProcEventInfo& eventInfo) { bool prevented = GetBase()->CallScriptEffectProcHandlers(this, aurApp, eventInfo); @@ -6200,31 +6264,6 @@ void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const { switch (GetSpellInfo()->SpellFamilyName) { - case SPELLFAMILY_DRUID: - { - switch (GetSpellInfo()->Id) - { - // Frenzied Regeneration - case 22842: - { - // Converts up to 10 rage per second into health for $d. Each point of rage is converted into ${$m2/10}.1% of max health. - // Should be manauser - if (!target->HasActivePowerType(POWER_RAGE)) - break; - uint32 rage = target->GetPower(POWER_RAGE); - // Nothing todo - if (rage == 0) - break; - int32 mod = (rage < 100) ? rage : 100; - int32 points = target->CalculateSpellDamage(target, GetSpellInfo(), 1); - int32 regen = target->GetMaxHealth() * (mod * points / 10) / 1000; - target->CastCustomSpell(target, 22845, ®en, 0, 0, true, 0, this); - target->SetPower(POWER_RAGE, rage - mod); - break; - } - } - break; - } case SPELLFAMILY_HUNTER: { // Explosive Shot @@ -6251,15 +6290,6 @@ void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const } break; } - case SPELLFAMILY_SHAMAN: - if (GetId() == 52179) // Astral Shift - { - // Periodic need for remove visual on stun/fear/silence lost - if (!(target->GetUnitFlags() & (UNIT_FLAG_STUNNED | UNIT_FLAG_FLEEING | UNIT_FLAG_SILENCED))) - target->RemoveAurasDueToSpell(52179); - break; - } - break; case SPELLFAMILY_DEATHKNIGHT: switch (GetId()) { @@ -6426,21 +6456,6 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster) } break; } - case SPELLFAMILY_SHAMAN: - { - switch (auraId) - { - // Lightning Shield (The Earthshatterer set trigger after cast Lighting Shield) - case 28820: - { - // Need remove self if Lightning Shield not active - if (!target->GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x400, 0, 0)) - target->RemoveAurasDueToSpell(28820); - return; - } - } - break; - } default: break; } @@ -6450,10 +6465,6 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster) // Spell exist but require custom code switch (auraId) { - // Mana Tide - case 16191: - target->CastCustomSpell(target, triggerSpellId, &m_amount, nullptr, nullptr, true, nullptr, this); - return; // Poison (Grobbulus) case 28158: case 54362: @@ -6670,18 +6681,6 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const damage = damageReductedArmor; } - // Curse of Agony damage-per-tick calculation - if (GetSpellInfo()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellInfo()->SpellFamilyFlags[0] & 0x400) && GetSpellInfo()->SpellIconID == 544) - { - uint32 totalTick = GetTotalTicks(); - // 1..4 ticks, 1/2 from normal tick damage - if (m_tickNumber <= totalTick / 3) - damage = damage / 2; - // 9..12 ticks, 3/2 from normal tick damage - else if (m_tickNumber > totalTick * 2 / 3) - damage += (damage + 1) / 2; // +1 prevent 0.5 damage possible lost at 1..4 ticks - // 5..8 ticks have normal tick damage - } } // calculate crit chance @@ -6748,7 +6747,7 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const Unit::DealDamage(caster, target, damage, &cleanDamage, DOT, GetSpellInfo()->GetSchoolMask(), GetSpellInfo(), true); - Unit::ProcDamageAndSpell(caster, target, caster ? procAttacker : 0, procVictim, procEx, damage, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, &dmgInfo); + Unit::ProcSkillsAndAuras(caster, target, caster ? procAttacker : 0, procVictim, procEx, damage, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, &dmgInfo); } void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) const @@ -6842,7 +6841,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c new_damage = Unit::DealDamage(caster, target, damage, &cleanDamage, DOT, GetSpellInfo()->GetSchoolMask(), GetSpellInfo(), false); - Unit::ProcDamageAndSpell(caster, target, caster ? procAttacker : 0, procVictim, procEx, damage, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, &dmgInfo); + Unit::ProcSkillsAndAuras(caster, target, caster ? procAttacker : 0, procVictim, procEx, damage, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, &dmgInfo); if (!caster || !caster->IsAlive()) return; @@ -6955,22 +6954,6 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const } else { - // Wild Growth = amount + (6 - 2*doneTicks) * ticks* amount / 100 - if (GetSpellInfo()->SpellFamilyName == SPELLFAMILY_DRUID && GetSpellInfo()->SpellIconID == 2864) - { - uint32 tickNumber = GetTickNumber() - 1; - int32 tempAmount = m_spellInfo->Effects[m_effIndex].CalcValue(caster, &m_baseAmount, nullptr); - - float drop = 2.0f; - - // Item - Druid T10 Restoration 2P Bonus - if (caster) - if (AuraEffect* aurEff = caster->GetAuraEffect(70658, 0)) - AddPct(drop, -aurEff->GetAmount()); - - damage += GetTotalTicks() * tempAmount * (6 - (drop * tickNumber)) * 0.01f; - } - if (GetBase()->GetType() == DYNOBJ_AURA_TYPE) damage = caster->SpellHealingBonusDone(target, GetSpellInfo(), damage, DOT, GetEffIndex(), 0.0f, GetBase()->GetStackAmount()); damage = target->SpellHealingBonusTaken(caster, GetSpellInfo(), damage, DOT, GetBase()->GetStackAmount()); @@ -7039,7 +7022,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const // ignore item heals if (!haveCastItem && GetAuraType() != SPELL_AURA_OBS_MOD_HEALTH) // xinef: dont allow obs_mod_health to proc spells, this is passive regeneration and not hot - Unit::ProcDamageAndSpell(caster, target, caster ? procAttacker : 0, procVictim, procEx, heal, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, nullptr, &healInfo); + Unit::ProcSkillsAndAuras(caster, target, caster ? procAttacker : 0, procVictim, procEx, heal, BASE_ATTACK, GetSpellInfo(), nullptr, GetEffIndex(), nullptr, nullptr, &healInfo); } void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) const @@ -7220,14 +7203,14 @@ void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) con // Set trigger flag uint32 procAttacker = PROC_FLAG_DONE_PERIODIC; uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC; - uint32 procEx = createProcExtendMask(&damageInfo, SPELL_MISS_NONE) | PROC_EX_INTERNAL_DOT; if (damageInfo.damage) procVictim |= PROC_FLAG_TAKEN_DAMAGE; caster->DealSpellDamage(&damageInfo, true); - DamageInfo dmgInfo(damageInfo, DOT); - Unit::ProcDamageAndSpell(caster, damageInfo.target, procAttacker, procVictim, procEx, damageInfo.damage, BASE_ATTACK, spellProto, nullptr, GetEffIndex(), nullptr, &dmgInfo); + DamageInfo dmgInfo(damageInfo, DOT, BASE_ATTACK, SPELL_MISS_NONE); + uint32 hitMask = dmgInfo.GetHitMask() | PROC_EX_INTERNAL_DOT; + Unit::ProcSkillsAndAuras(caster, damageInfo.target, procAttacker, procVictim, hitMask, damageInfo.damage, BASE_ATTACK, spellProto, nullptr, GetEffIndex(), nullptr, &dmgInfo); } void AuraEffect::HandleProcTriggerSpellAuraProc(AuraApplication* aurApp, ProcEventInfo& eventInfo) diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h index 8349c83fc..937195060 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.h +++ b/src/server/game/Spells/Auras/SpellAuraEffects.h @@ -98,6 +98,7 @@ public: void PeriodicTick(AuraApplication* aurApp, Unit* caster) const; void HandleProc(AuraApplication* aurApp, ProcEventInfo& eventInfo); + bool CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventInfo) const; void CleanupTriggeredSpells(Unit* target); diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index 0b3b35d59..63da507eb 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -990,12 +990,6 @@ bool Aura::ModStackAmount(int32 num, AuraRemoveMode removeMode, bool periodicRes // reset charges SetCharges(CalcMaxCharges()); - // FIXME: not a best way to synchronize charges, but works - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (AuraEffect* aurEff = GetEffect(i)) - if (aurEff->GetAuraType() == SPELL_AURA_ADD_FLAT_MODIFIER || aurEff->GetAuraType() == SPELL_AURA_ADD_PCT_MODIFIER) - if (SpellModifier* mod = aurEff->GetSpellModifier()) - mod->charges = GetCharges(); } SetStackAmount(stackAmount); @@ -1772,24 +1766,13 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b } break; case SPELLFAMILY_ROGUE: + // Remove Vanish on stealth remove + if (GetId() == 1784) { - // Overkill, Master of Subtlety - if (caster && GetSpellInfo()->SpellIconID == 250) - { - if (caster->GetDummyAuraEffect(SPELLFAMILY_ROGUE, 2114, 0)) - caster->CastSpell(caster, 31666, true); - - if (caster->GetAuraEffectDummy(58426)) - caster->CastSpell(caster, 58428, true); - } - // Remove Vanish on stealth remove - if (GetId() == 1784) - { - target->RemoveAurasWithFamily(SPELLFAMILY_ROGUE, 0x800, 0, 0, ObjectGuid::Empty); - target->RemoveAurasDueToSpell(18461); - } - break; + target->RemoveAurasWithFamily(SPELLFAMILY_ROGUE, 0x800, 0, 0, ObjectGuid::Empty); + target->RemoveAurasDueToSpell(18461); } + break; case SPELLFAMILY_SHAMAN: { // Ghost Wolf Speed (PvP 58 lvl set) @@ -1839,6 +1822,39 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b // mods at aura apply or remove switch (GetSpellInfo()->SpellFamilyName) { + case SPELLFAMILY_ROGUE: + // Stealth + if (GetSpellInfo()->SpellFamilyFlags[0] & 0x00400000) + { + // Master of Subtlety + if (AuraEffect const* aurEff = target->GetAuraEffectOfRankedSpell(31221, 0)) + { + if (!apply) + target->CastSpell(target, 31666, true); + else + { + // Remove counter aura + target->RemoveAurasDueToSpell(31666); + + int32 amount = aurEff->GetAmount(); + target->CastCustomSpell(target, 31665, &amount, nullptr, nullptr, true); + } + } + // Overkill + if (target->HasAura(58426)) + { + if (!apply) + target->CastSpell(target, 58428, true); + else + { + // Remove counter aura + target->RemoveAurasDueToSpell(58428); + + target->CastSpell(target, 58427, true); + } + } + } + break; case SPELLFAMILY_HUNTER: switch (GetId()) { @@ -1871,24 +1887,8 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b case SPELLFAMILY_PALADIN: switch (GetId()) { - case 19746: - case 31821: - // Aura Mastery Triggered Spell Handler - // If apply Concentration Aura -> trigger -> apply Aura Mastery Immunity - // If remove Concentration Aura -> trigger -> remove Aura Mastery Immunity - // If remove Aura Mastery -> trigger -> remove Aura Mastery Immunity - // Do effects only on aura owner - if (GetCasterGUID() != target->GetGUID()) - break; - if (apply) - { - if ((GetSpellInfo()->Id == 31821 && target->HasAura(19746, GetCasterGUID())) || (GetSpellInfo()->Id == 19746 && target->HasAura(31821))) - target->CastSpell(target, 64364, true); - } - else - target->RemoveAurasDueToSpell(64364, GetCasterGUID()); - break; - case 31842: + case 31842: // Divine Illumination + // Item - Paladin T10 Holy 2P Bonus if (caster && caster->HasAura(70755)) { if (apply) @@ -1898,23 +1898,6 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b } break; } - - // Sanctified Retribution, Improved Devotion Aura, Swift Retribution, Improved Concentration Aura - if (caster == target && GetSpellInfo()->GetSpellSpecific() == SPELL_SPECIFIC_AURA) - { - if (apply) - { - target->CastSpell(target, 63510, true); - target->CastSpell(target, 63514, true); - target->CastSpell(target, 63531, true); - } - else - { - target->RemoveAura(63510); - target->RemoveAura(63514); - target->RemoveAura(63531); - } - } break; } } @@ -1990,8 +1973,13 @@ bool Aura::CanStackWith(Aura const* existingAura) const } // passive auras don't stack with another rank of the spell cast by same caster + // Exception: item-sourced auras from different items can stack (e.g., weapon imbues on MH/OH) if (IsPassive() && sameCaster && (m_spellInfo->IsDifferentRankOf(existingSpellInfo) || (m_spellInfo->Id == existingSpellInfo->Id && m_castItemGuid.IsEmpty()))) - return false; + { + // Allow stacking if both auras are from different items + if (!(GetCastItemGUID() && existingAura->GetCastItemGUID() && GetCastItemGUID() != existingAura->GetCastItemGUID())) + return false; + } for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { @@ -2102,9 +2090,11 @@ bool Aura::CanStackWith(Aura const* existingAura) const // don't allow passive area auras to stack if (m_spellInfo->IsMultiSlotAura() && !IsArea()) return true; - if (GetCastItemGUID() && existingAura->GetCastItemGUID()) - if (GetCastItemGUID() != existingAura->GetCastItemGUID() && m_spellInfo->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC)) - return true; + + // Allow item-sourced auras from different items to stack (e.g., weapon imbues on MH/OH, enchant procs) + if ((IsPassive() || m_spellInfo->HasAttribute(SPELL_ATTR0_CU_ENCHANT_PROC)) && GetCastItemGUID() && existingAura->GetCastItemGUID() && GetCastItemGUID() != existingAura->GetCastItemGUID()) + return true; + // same spell with same caster should not stack return false; } @@ -2112,22 +2102,32 @@ bool Aura::CanStackWith(Aura const* existingAura) const return true; } -bool Aura::IsProcOnCooldown() const +bool Aura::IsProcOnCooldown(TimePoint now) const { - /*if (m_procCooldown) - { - if (m_procCooldown > GameTime::GetGameTime().count()) - return true; - }*/ - return false; + if (GetType() == UNIT_AURA_TYPE) + if (Player* player = GetUnitOwner()->ToPlayer()) + if (player->GetCommandStatus(CHEAT_COOLDOWN)) + return false; + + return m_procCooldown > now; } -void Aura::AddProcCooldown(Milliseconds /*msec*/) +void Aura::AddProcCooldown(TimePoint cooldownEnd) { - //m_procCooldown = std:chrono::steady_clock::now() + msec; + m_procCooldown = cooldownEnd; } -void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo) +void Aura::AddProcCooldown(SpellProcEntry const* procEntry, TimePoint now) +{ + AddProcCooldown(now + procEntry->Cooldown); +} + +void Aura::ResetProcCooldown() +{ + m_procCooldown = std::chrono::steady_clock::now(); +} + +void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) { bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo); if (!prepare) @@ -2145,50 +2145,88 @@ void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInf ASSERT(procEntry); // cooldowns should be added to the whole aura (see 51698 area aura) - AddProcCooldown(procEntry->Cooldown); + AddProcCooldown(now + procEntry->Cooldown); } -bool Aura::IsProcTriggeredOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo) const +uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) const { SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetId()); + // only auras with spell proc entry can trigger proc if (!procEntry) - return false; + return 0; + + // check spell triggering us + if (Spell const* spell = eventInfo.GetProcSpell()) + { + // Do not allow auras to proc from effect triggered from itself + if (spell->GetTriggeredByAuraSpellInfo() == m_spellInfo) + return 0; + + // check if aura can proc when spell is triggered (exception for hunter auto shot & wands) + if (!GetSpellInfo()->HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS) && + !(procEntry->AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) && + !(eventInfo.GetTypeMask() & AUTO_ATTACK_PROC_FLAG_MASK)) + { + if (spell->IsTriggered() && !spell->GetSpellInfo()->HasAttribute(SPELL_ATTR3_NOT_A_PROC)) + return 0; + } + + // do not allow aura proc if proc is caused by a spell cast by item + if (spell->m_CastItem && (procEntry->AttributesMask & PROC_ATTR_CANT_PROC_FROM_ITEM_CAST)) + return 0; + } // check if we have charges to proc with if (IsUsingCharges() && !GetCharges()) - return false; + return 0; // check proc cooldown - if (IsProcOnCooldown()) - return false; - - /// @todo: - // something about triggered spells triggering, and add extra attack effect + if (IsProcOnCooldown(now)) + return 0; // do checks against db data if (!sSpellMgr->CanSpellTriggerProcOnEvent(*procEntry, eventInfo)) - return false; + return 0; + + // check if spell was affected by this aura's spellmod (used by Arcane Potency and similar effects) + // only applies when consuming charges or stacks + if ((procEntry->AttributesMask & PROC_ATTR_REQ_SPELLMOD) && (IsUsingCharges() || (procEntry->AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES))) + { + Spell const* procSpell = eventInfo.GetProcSpell(); + if (!procSpell || procSpell->m_appliedMods.find(const_cast(this)) == procSpell->m_appliedMods.end()) + return 0; + } // do checks using conditions table ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_SPELL_PROC, GetId()); ConditionSourceInfo condInfo = ConditionSourceInfo(eventInfo.GetActor(), eventInfo.GetActionTarget()); if (!sConditionMgr->IsObjectMeetToConditions(condInfo, conditions)) - return false; + return 0; // AuraScript Hook - bool check = const_cast(this)->CallScriptCheckProcHandlers(aurApp, eventInfo); - if (!check) - return false; + if (!const_cast(this)->CallScriptCheckProcHandlers(aurApp, eventInfo)) + return 0; - /// @todo: - // do allow additional requirements for procs - // this is needed because this is the last moment in which you can prevent aura charge drop on proc - // and possibly a way to prevent default checks (if there're going to be any) + // At least one effect has to pass checks to proc aura + uint8 procEffectMask = aurApp->GetEffectMask(); + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (AuraEffect* aurEff = GetEffect(i)) + { + if (procEffectMask & (1 << i)) + { + if ((procEntry->DisableEffectsMask & (1u << i)) || !aurEff->CheckEffectProc(aurApp, eventInfo)) + procEffectMask &= ~(1 << i); + } + } + } + + if (!procEffectMask) + return 0; // Check if current equipment meets aura requirements // do that only for passive spells - /// @todo: this needs to be unified for all kinds of auras Unit* target = aurApp->GetTarget(); if (IsPassive() && target->IsPlayer() && GetSpellInfo()->EquippedItemClass != -1) { @@ -2198,7 +2236,7 @@ bool Aura::IsProcTriggeredOnEvent(AuraApplication* aurApp, ProcEventInfo& eventI if (GetSpellInfo()->EquippedItemClass == ITEM_CLASS_WEAPON) { if (target->ToPlayer()->IsInFeralForm()) - return false; + return 0; if (DamageInfo const* damageInfo = eventInfo.GetDamageInfo()) { @@ -2223,13 +2261,15 @@ bool Aura::IsProcTriggeredOnEvent(AuraApplication* aurApp, ProcEventInfo& eventI } if (!item || item->IsBroken() || !item->IsFitToSpellRequirements(GetSpellInfo())) - { return 0; - } } } - return roll_chance_f(CalcProcChance(*procEntry, eventInfo)); + float procChance = CalcProcChance(*procEntry, eventInfo); + if (roll_chance_f(procChance)) + return procEffectMask; + + return 0; } float Aura::CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const @@ -2239,33 +2279,71 @@ float Aura::CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& event // so talents modifying chances and judgements will have properly calculated proc chance if (Unit* caster = GetCaster()) { - // calculate ppm chance if present and we're using weapon + // If PPM exists calculate chance from PPM if (eventInfo.GetDamageInfo() && procEntry.ProcsPerMinute != 0) { - uint32 WeaponSpeed = caster->GetAttackTime(eventInfo.GetDamageInfo()->GetAttackType()); - chance = caster->GetPPMProcChance(WeaponSpeed, procEntry.ProcsPerMinute, GetSpellInfo()); + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + uint32 attackSpeed = 0; + if (!procSpell || procSpell->DmgClass == SPELL_DAMAGE_CLASS_MELEE || procSpell->IsRangedWeaponSpell()) + { + attackSpeed = caster->GetAttackTime(eventInfo.GetDamageInfo()->GetAttackType()); + } + else // spells use their cast time for PPM calculations + { + if (procSpell->CastTimeEntry) + attackSpeed = procSpell->CastTimeEntry->CastTime; + + // instants and fast spells use 1.5s cast speed + if (attackSpeed < 1500) + attackSpeed = 1500; + } + chance = caster->GetPPMProcChance(attackSpeed, procEntry.ProcsPerMinute, GetSpellInfo()); } // apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetId(), SPELLMOD_CHANCE_OF_SUCCESS, chance); } + + // proc chance is reduced by an additional 3.333% per level past 60 + if ((procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60) && eventInfo.GetActor()->GetLevel() > 60) + chance = std::max(0.f, (1.f - ((eventInfo.GetActor()->GetLevel() - 60) * 1.f / 30.f)) * chance); + return chance; } -void Aura::TriggerProcOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo) +void Aura::TriggerProcOnEvent(uint8 procEffectMask, AuraApplication* aurApp, ProcEventInfo& eventInfo) { - CallScriptProcHandlers(aurApp, eventInfo); + bool prevented = CallScriptProcHandlers(aurApp, eventInfo); + if (!prevented) + { + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(procEffectMask & (1 << i))) + continue; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (aurApp->HasEffect(i)) // OnEffectProc / AfterEffectProc hooks handled in AuraEffect::HandleProc() - GetEffect(i)->HandleProc(aurApp, eventInfo); + if (aurApp->HasEffect(i)) + GetEffect(i)->HandleProc(aurApp, eventInfo); + } - CallScriptAfterProcHandlers(aurApp, eventInfo); + CallScriptAfterProcHandlers(aurApp, eventInfo); + } + ConsumeProcCharges(sSpellMgr->GetSpellProcEntry(GetId())); +} + +void Aura::ConsumeProcCharges(SpellProcEntry const* procEntry) +{ // Remove aura if we've used last charge to proc - if (IsUsingCharges() && !GetCharges()) - Remove(); + if (procEntry->AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES) + { + ModStackAmount(-1); + } + else if (IsUsingCharges()) + { + if (!GetCharges()) + Remove(); + } } void Aura::_DeleteRemovedApplications() @@ -2570,6 +2648,23 @@ bool Aura::CallScriptCheckProcHandlers(AuraApplication const* aurApp, ProcEventI return result; } +bool Aura::CallScriptCheckEffectProcHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo) +{ + bool result = true; + for (std::list::iterator scritr = m_loadedScripts.begin(); scritr != m_loadedScripts.end(); ++scritr) + { + (*scritr)->_PrepareScriptCall(AURA_SCRIPT_HOOK_CHECK_EFFECT_PROC, aurApp); + std::list::iterator effEndItr = (*scritr)->DoCheckEffectProc.end(), effItr = (*scritr)->DoCheckEffectProc.begin(); + for (; effItr != effEndItr; ++effItr) + if (effItr->IsEffectAffected(m_spellInfo, aurEff->GetEffIndex())) + result &= effItr->Call(*scritr, aurEff, eventInfo); + + (*scritr)->_FinishScriptCall(); + } + + return result; +} + bool Aura::CallScriptAfterCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo, bool isTriggeredAtSpellProcEvent) { bool result = isTriggeredAtSpellProcEvent; diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h index 8731299f8..5d8afc9a4 100644 --- a/src/server/game/Spells/Auras/SpellAuras.h +++ b/src/server/game/Spells/Auras/SpellAuras.h @@ -193,17 +193,17 @@ public: bool IsAuraStronger(Aura const* newAura) const; // Proc system - // this subsystem is not yet in use - the core of it is functional, but still some research has to be done - // and some dependant problems fixed before it can replace old proc system (for example cooldown handling) - // currently proc system functionality is implemented in Unit::ProcDamageAndSpell - bool IsProcOnCooldown() const; - void AddProcCooldown(Milliseconds msec); + bool IsProcOnCooldown(TimePoint now) const; + void AddProcCooldown(TimePoint cooldownEnd); + void AddProcCooldown(SpellProcEntry const* procEntry, TimePoint now); + void ResetProcCooldown(); bool IsUsingCharges() const { return m_isUsingCharges; } void SetUsingCharges(bool val) { m_isUsingCharges = val; } - void PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo); - bool IsProcTriggeredOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo) const; + void PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now); + uint8 GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) const; float CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const; - void TriggerProcOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo); + void TriggerProcOnEvent(uint8 procEffectMask, AuraApplication* aurApp, ProcEventInfo& eventInfo); + void ConsumeProcCharges(SpellProcEntry const* procEntry); // AuraScript void LoadScripts(); @@ -227,6 +227,7 @@ public: // Spell Proc Hooks bool CallScriptCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo); + bool CallScriptCheckEffectProcHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo); bool CallScriptAfterCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo, bool isTriggeredAtSpellProcEvent); bool CallScriptPrepareProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo); bool CallScriptProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo); @@ -270,6 +271,8 @@ protected: bool m_isSingleTarget: 1; // true if it's a single target spell and registered at caster - can change at spell steal for example bool m_isUsingCharges: 1; + TimePoint m_procCooldown; + private: Unit::AuraApplicationList m_removedApplications; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 377b15ef2..02cea67fd 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2267,7 +2267,7 @@ void Spell::prepareDataForTriggerSystem(AuraEffect const* /*triggeredByAura*/) else if (HasTriggeredCastFlag(TRIGGERED_DISALLOW_PROC_EVENTS)) m_procEx |= PROC_EX_INTERNAL_TRIGGERED; } - // Totem casts require spellfamilymask defined in spell_proc_event to proc + // Totem casts require spellfamilymask defined in spell_proc to proc if (m_originalCaster && m_caster != m_originalCaster && m_caster->IsCreature() && m_caster->ToCreature()->IsTotem() && m_caster->IsControlledByPlayer()) m_procEx |= PROC_EX_INTERNAL_REQ_FAMILY; } @@ -2648,7 +2648,6 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Fill base trigger info uint32 procAttacker = m_procAttacker; uint32 procVictim = m_procVictim; - uint32 procEx = m_procEx; // Trigger info was not filled in spell::preparedatafortriggersystem - we do it now if (canEffectTrigger && !procAttacker && !procVictim) @@ -2705,20 +2704,21 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) uint32 addhealth = m_healing; if (crit) - { - procEx |= PROC_EX_CRITICAL_HIT; addhealth = Unit::SpellCriticalHealingBonus(caster, m_spellInfo, addhealth, nullptr); - } - else - procEx |= PROC_EX_NORMAL_HIT; HealInfo healInfo(caster, unitTarget, addhealth, m_spellInfo, m_spellInfo->GetSchoolMask()); + // Set hitMask based on crit + if (crit) + healInfo.AddHitMask(PROC_HIT_CRITICAL); + else + healInfo.AddHitMask(PROC_HIT_NORMAL); + // Xinef: override with forced crit, only visual result if (GetSpellValue()->ForcedCritResult) { crit = true; - procEx |= PROC_EX_CRITICAL_HIT; + healInfo.AddHitMask(PROC_HIT_CRITICAL); } int32 gain = caster->HealBySpell(healInfo, crit); @@ -2729,14 +2729,17 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) unitTarget->getHostileRefMgr().threatAssist(caster, threat, m_spellInfo); m_healing = gain; - // Xinef: if heal acutally healed something, add no overheal flag + // Xinef: if heal actually healed something, add no overheal flag if (m_healing) - procEx |= PROC_EX_NO_OVERHEAL; + healInfo.AddHitMask(PROC_EX_NO_OVERHEAL); // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) if (canEffectTrigger) - Unit::ProcDamageAndSpell(caster, unitTarget, procAttacker, procVictim, procEx, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + { + uint32 hitMask = m_procEx | healInfo.GetHitMask(); + Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, m_triggeredByAuraSpell.effectIndex, this, nullptr, &healInfo); + } } // Do damage and triggers else if (m_damage > 0) @@ -2802,7 +2805,6 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) if (reflectedSpell) effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit); - procEx |= createProcExtendMask(&damageInfo, missInfo); procVictim |= PROC_FLAG_TAKEN_DAMAGE; caster->DealSpellDamage(&damageInfo, true, this); @@ -2813,13 +2815,14 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) if (canEffectTrigger) { - DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE); - Unit::ProcDamageAndSpell(caster, unitTarget, procAttacker, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo); + uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); + Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx); + caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask()); } m_damage = damageInfo.damage; @@ -2829,19 +2832,19 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) { // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_spellSchoolMask); - procEx |= createProcExtendMask(&damageInfo, missInfo); // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) if (canEffectTrigger) { - DamageInfo dmgInfo(damageInfo, NODAMAGE); - Unit::ProcDamageAndSpell(caster, unitTarget, procAttacker, procVictim, procEx, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + DamageInfo dmgInfo(damageInfo, NODAMAGE, m_attackType, missInfo); + uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); + Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); // Xinef: eg. rogue poisons can proc off cheap shot, etc. so this block should be here also // Xinef: ofc count only spells that HIT the target, little hack used to fool the system - if ((procEx & PROC_EX_NORMAL_HIT || procEx & PROC_EX_CRITICAL_HIT) && caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && + if ((dmgInfo.GetHitMask() & (PROC_HIT_NORMAL | PROC_HIT_CRITICAL)) && caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim | PROC_FLAG_TAKEN_DAMAGE, procEx); + caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim | PROC_FLAG_TAKEN_DAMAGE, dmgInfo.GetHitMask()); } // Failed Pickpocket, reveal rogue @@ -3194,20 +3197,6 @@ void Spell::DoTriggersOnSpellHit(Unit* unit, uint8 effMask) /// @todo: move this code to scripts if (m_preCastSpell) { - // Paladin immunity shields - if (m_preCastSpell == 61988) - { - // Cast Forbearance - m_caster->CastSpell(unit, 25771, true); - // Cast Avenging Wrath Marker - unit->CastSpell(unit, 61987, true); - } - - // Avenging Wrath - if (m_preCastSpell == 61987) - // Cast the serverside immunity shield marker - m_caster->CastSpell(unit, 61988, true); - // Fearie Fire (Feral) - damage if (m_preCastSpell == 60089) m_caster->CastSpell(unit, m_preCastSpell, true); @@ -3896,48 +3885,6 @@ void Spell::_cast(bool skipCheck) // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... SendSpellGo(); - if (modOwner) - modOwner->SetSpellModTakingSpell(this, false); - - if (m_originalCaster) - { - // Handle procs on cast - uint32 procAttacker = m_procAttacker; - if (!procAttacker) - { - bool IsPositive = m_spellInfo->IsPositive(); - if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) - { - procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; - } - else - { - procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; - } - } - - uint32 procEx = PROC_EX_NORMAL_HIT; - - for (std::list::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - { - if (ihit->missCondition != SPELL_MISS_NONE) - { - continue; - } - - if (!ihit->crit) - { - continue; - } - - procEx |= PROC_EX_CRITICAL_HIT; - break; - } - - Unit::ProcDamageAndSpell(m_originalCaster, m_originalCaster, procAttacker, PROC_FLAG_NONE, procEx, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, - m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST); - } - if (modOwner) modOwner->SetSpellModTakingSpell(this, true); @@ -4012,6 +3959,46 @@ void Spell::_cast(bool skipCheck) if (modOwner) modOwner->SetSpellModTakingSpell(this, false); + // Handle procs on cast - only for non-triggered spells + // Triggered spells (from auras, items, etc.) should not fire CAST phase procs + // as they are not player-initiated casts. This prevents issues like Arcane Potency + // charges being consumed by periodic damage effects (e.g., Blizzard ticks). + // Must be called AFTER handle_immediate() so spell mods (like Missile Barrage's + // duration reduction) are applied before the aura is consumed by the proc. + if (m_originalCaster && !IsTriggered()) + { + uint32 procAttacker = m_procAttacker; + if (!procAttacker) + { + bool IsPositive = m_spellInfo->IsPositive(); + if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) + { + procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; + } + else + { + procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; + } + } + + uint32 hitMask = PROC_HIT_NORMAL; + + for (std::list::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->missCondition != SPELL_MISS_NONE) + continue; + + if (!ihit->crit) + continue; + + hitMask |= PROC_HIT_CRITICAL; + break; + } + + Unit::ProcSkillsAndAuras(m_originalCaster, m_originalCaster, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST); + } + if (std::vector const* spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id)) { for (int32 id : *spell_triggered) @@ -4269,24 +4256,20 @@ void Spell::_handle_finish_phase() } } - uint32 procEx = PROC_EX_NORMAL_HIT; + uint32 hitMask = PROC_HIT_NORMAL; for (std::list::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { if (ihit->missCondition != SPELL_MISS_NONE) - { continue; - } if (!ihit->crit) - { continue; - } - procEx |= PROC_EX_CRITICAL_HIT; + hitMask |= PROC_HIT_CRITICAL; break; } - Unit::ProcDamageAndSpell(m_originalCaster, m_originalCaster, procAttacker, PROC_FLAG_NONE, procEx, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + Unit::ProcSkillsAndAuras(m_originalCaster, m_originalCaster, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo, m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_FINISH); } } @@ -8209,7 +8192,7 @@ bool ReflectEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { Unit* target = ObjectAccessor::GetUnit(*_caster, _targetGUID); if (target && _caster->IsInMap(target)) - Unit::ProcDamageAndSpell(_caster, target, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, _spellInfo); + Unit::ProcSkillsAndAuras(_caster, target, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, _spellInfo); return true; } diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 0f8cde5d3..ea9fe3630 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -599,6 +599,7 @@ public: std::list* GetUniqueTargetInfo() { return &m_UniqueTargetInfo; } [[nodiscard]] uint32 GetTriggeredByAuraTickNumber() const { return m_triggeredByAuraSpell.tickNumber; } + [[nodiscard]] SpellInfo const* GetTriggeredByAuraSpellInfo() const { return m_triggeredByAuraSpell.spellInfo; } [[nodiscard]] TriggerCastFlags GetTriggeredCastFlags() const { return _triggeredCastFlags; } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index d725b682e..5aff04864 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -3927,39 +3927,6 @@ void Spell::EffectScriptEffect(SpellEffIndex effIndex) return; } - // Stoneclaw Totem - case 55328: // Rank 1 - case 55329: // Rank 2 - case 55330: // Rank 3 - case 55332: // Rank 4 - case 55333: // Rank 5 - case 55335: // Rank 6 - case 55278: // Rank 7 - case 58589: // Rank 8 - case 58590: // Rank 9 - case 58591: // Rank 10 - { - int32 basepoints0 = damage; - // Cast Absorb on totems - for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) - { - if (!unitTarget->m_SummonSlot[slot]) - continue; - - Creature* totem = unitTarget->GetMap()->GetCreature(unitTarget->m_SummonSlot[slot]); - if (totem && totem->IsTotem()) - { - m_caster->CastCustomSpell(totem, 55277, &basepoints0, nullptr, nullptr, true); - } - } - // Glyph of Stoneclaw Totem - if (AuraEffect* aur = unitTarget->GetAuraEffect(63298, 0)) - { - basepoints0 *= aur->GetAmount(); - m_caster->CastCustomSpell(unitTarget, 55277, &basepoints0, nullptr, nullptr, true); - } - break; - } case 61263: // for item Intravenous Healing Potion (44698) { if (!m_caster || !unitTarget) diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 74b89e99b..209a54923 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1313,7 +1313,6 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const if (affectSpell->SpellFamilyName != SpellFamilyName) return false; - // true if (mod->mask & SpellFamilyFlags) return true; diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index 3abafbc05..66e77fa95 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -1236,15 +1236,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].SpellClassMask[1] |= 0x20; // prayer of mending }); - // Power Infusion - ApplySpellFix({ 10060 }, [](SpellInfo* spellInfo) - { - // hack to fix stacking with arcane power - spellInfo->Effects[EFFECT_2].Effect = SPELL_EFFECT_APPLY_AURA; - spellInfo->Effects[EFFECT_2].ApplyAuraName = SPELL_AURA_ADD_PCT_MODIFIER; - spellInfo->Effects[EFFECT_2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ALLY); - }); - // Lifebloom final bloom ApplySpellFix({ 33778 }, [](SpellInfo* spellInfo) { @@ -3695,13 +3686,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_MOD_CHARM; }); - // Persistent Shield - ApplySpellFix({ 26467 }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE; - spellInfo->Effects[EFFECT_0].TriggerSpell = 26470; - }); - // Deadly Swiftness ApplySpellFix({ 31255 }, [](SpellInfo* spellInfo) { diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 49c6558c8..a2006ba45 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -784,166 +784,6 @@ SpellGroupStackRule SpellMgr::GetSpellGroupStackRule(SpellGroup group) const return SPELL_GROUP_STACK_RULE_DEFAULT; } -SpellProcEventEntry const* SpellMgr::GetSpellProcEvent(uint32 spellId) const -{ - SpellProcEventMap::const_iterator itr = mSpellProcEventMap.find(spellId); - if (itr != mSpellProcEventMap.end()) - return &itr->second; - return nullptr; -} - -bool SpellMgr::IsSpellProcEventCanTriggeredBy(SpellInfo const* spellProto, SpellProcEventEntry const* spellProcEvent, uint32 EventProcFlag, ProcEventInfo const& eventInfo, bool active) const -{ - // No extra req need - uint32 procEvent_procEx = PROC_EX_NONE; - uint32 procEvent_procPhase = PROC_SPELL_PHASE_HIT; - - uint32 procFlags = eventInfo.GetTypeMask(); - uint32 procExtra = eventInfo.GetHitMask(); - uint32 procPhase = eventInfo.GetSpellPhaseMask(); - SpellInfo const* procSpellInfo = eventInfo.GetSpellInfo(); - - // check prockFlags for condition - if ((procFlags & EventProcFlag) == 0) - return false; - - // Xinef: Always trigger for this, including TAKEN_DAMAGE - if (EventProcFlag & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH | PROC_FLAG_TAKEN_DAMAGE)) - return true; - - bool hasFamilyMask = false; - - if (procFlags & PROC_FLAG_DONE_PERIODIC) - { - if (procExtra & PROC_EX_INTERNAL_HOT) - { - if (EventProcFlag == PROC_FLAG_DONE_PERIODIC) - { - /// no aura with only PROC_FLAG_DONE_PERIODIC and spellFamilyName == 0 can proc from a HOT. - if (!spellProto->SpellFamilyName) - return false; - } - /// Aura must have positive procflags for a HOT to proc - else if (!(EventProcFlag & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS))) - return false; - } - /// Aura must have negative or neutral(PROC_FLAG_DONE_PERIODIC only) procflags for a DOT to proc - else if (EventProcFlag != PROC_FLAG_DONE_PERIODIC) - if (!(EventProcFlag & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG | PROC_FLAG_DONE_TRAP_ACTIVATION))) - return false; - } - - if (procFlags & PROC_FLAG_TAKEN_PERIODIC) - { - if (procExtra & PROC_EX_INTERNAL_HOT) - { - /// No aura that only has PROC_FLAG_TAKEN_PERIODIC can proc from a HOT. - if (EventProcFlag == PROC_FLAG_TAKEN_PERIODIC) - return false; - /// Aura must have positive procflags for a HOT to proc - if (!(EventProcFlag & (PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS))) - return false; - } - /// Aura must have negative or neutral(PROC_FLAG_TAKEN_PERIODIC only) procflags for a DOT to proc - else if (EventProcFlag != PROC_FLAG_TAKEN_PERIODIC) - if (!(EventProcFlag & (PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG))) - return false; - } - - // Trap casts are active by default - if (procFlags & PROC_FLAG_DONE_TRAP_ACTIVATION) - active = true; - - if (spellProcEvent) // Exist event data - { - // Store extra req - procEvent_procEx = spellProcEvent->procEx; - procEvent_procPhase = spellProcEvent->procPhase; - - // For melee triggers - if (!procSpellInfo) - { - // Check (if set) for school (melee attack have Normal school) - if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) - return false; - } - else // For spells need check school/spell family/family mask - { - // Check (if set) for school - if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & procSpellInfo->SchoolMask) == 0) - return false; - - // Check (if set) for spellFamilyName - if (spellProcEvent->spellFamilyName && (spellProcEvent->spellFamilyName != procSpellInfo->SpellFamilyName)) - return false; - - // spellFamilyName is Ok need check for spellFamilyMask if present - if (spellProcEvent->spellFamilyMask) - { - if (!(spellProcEvent->spellFamilyMask & procSpellInfo->SpellFamilyFlags)) - return false; - hasFamilyMask = true; - // Some spells are not considered as active even with have spellfamilyflags - if (!(procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL)) - active = true; - } - - // Check tick numbers - if (procEvent_procEx & PROC_EX_ONLY_FIRST_TICK) - { - if (Spell const* procSpell = eventInfo.GetProcSpell()) - { - if (procSpell->GetTriggeredByAuraTickNumber() > 1) - { - return false; - } - } - } - } - } - - if (procExtra & (PROC_EX_INTERNAL_REQ_FAMILY)) - { - if (!hasFamilyMask) - return false; - } - - if (!(procEvent_procPhase & procPhase)) - { - return false; - } - - // Check for extra req (if none) and hit/crit - if (procEvent_procEx == PROC_EX_NONE) - { - // No extra req, so can trigger only for hit/crit - spell has to be active - if ((procExtra & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) && active) - return true; - } - else // Passive spells hits here only if resist/reflect/immune/evade - { - if (procExtra & AURA_SPELL_PROC_EX_MASK) - { - // if spell marked as procing only from not active spells - if (active && procEvent_procEx & PROC_EX_NOT_ACTIVE_SPELL) - return false; - // if spell marked as procing only from active spells - if (!active && procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL) - return false; - // Exist req for PROC_EX_EX_TRIGGER_ALWAYS - if (procEvent_procEx & PROC_EX_EX_TRIGGER_ALWAYS) - return true; - // PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before - if ((procExtra & (PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT)) && ((procEvent_procEx & (AURA_SPELL_PROC_EX_MASK)) == 0)) - return true; - } - // Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other) - if (procEvent_procEx & procExtra) - return true; - } - return false; -} - SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const { SpellProcMap::const_iterator itr = mSpellProcMap.find(spellId); @@ -964,6 +804,14 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE if (eventInfo.GetActionTarget() && !actor->isHonorOrXPTarget(eventInfo.GetActionTarget())) return false; + // check mana cost requirement (used by Clearcasting and similar effects) + if (procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || (!spellInfo->ManaCost && !spellInfo->ManaCostPercentage)) + return false; + } + // always trigger for these types if (eventInfo.GetTypeMask() & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH)) return true; @@ -973,13 +821,16 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE return false; // check spell family name/flags (if set) for spells - if (eventInfo.GetTypeMask() & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION)) + if (eventInfo.GetTypeMask() & SPELL_PROC_FLAG_MASK) { - if (procEntry.SpellFamilyName && (procEntry.SpellFamilyName != eventInfo.GetSpellInfo()->SpellFamilyName)) - return false; + if (SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo()) + { + if (procEntry.SpellFamilyName && procEntry.SpellFamilyName != eventSpellInfo->SpellFamilyName) + return false; - if (procEntry.SpellFamilyMask && !(procEntry.SpellFamilyMask & eventInfo.GetSpellInfo()->SpellFamilyFlags)) - return false; + if (procEntry.SpellFamilyMask && !(procEntry.SpellFamilyMask & eventSpellInfo->SpellFamilyFlags)) + return false; + } } // check spell type mask (if set) @@ -996,8 +847,11 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE return false; } - // check hit mask (on taken hit or on done hit, but not on spell cast phase) - if ((eventInfo.GetTypeMask() & TAKEN_HIT_PROC_FLAG_MASK) || ((eventInfo.GetTypeMask() & DONE_HIT_PROC_FLAG_MASK) && !(eventInfo.GetSpellPhaseMask() & PROC_SPELL_PHASE_CAST))) + // check hit mask (on taken hit or on done hit) + // For CAST phase with DONE flags, only check if HitMask is explicitly set (crit is pre-calculated for travel time spells) + if ((eventInfo.GetTypeMask() & TAKEN_HIT_PROC_FLAG_MASK) || + ((eventInfo.GetTypeMask() & DONE_HIT_PROC_FLAG_MASK) && + (!(eventInfo.GetSpellPhaseMask() & PROC_SPELL_PHASE_CAST) || procEntry.HitMask))) { uint32 hitMask = procEntry.HitMask; // get default values if hit mask not set @@ -1916,98 +1770,65 @@ void SpellMgr::LoadSpellGroupStackRules() LOG_INFO("server.loading", " "); } -void SpellMgr::LoadSpellProcEvents() +static bool InitTriggerAuraData(); +static bool isTriggerAura[TOTAL_AURAS]; +static bool isAlwaysTriggeredAura[TOTAL_AURAS]; +static bool procPrepared = InitTriggerAuraData(); + +// List of auras that CAN trigger but may not exist in spell_proc +// in most case need for drop charges +// in some types of aura need do additional check +// for example SPELL_AURA_MECHANIC_IMMUNITY - need check for mechanic +bool InitTriggerAuraData() { - uint32 oldMSTime = getMSTime(); - - mSpellProcEventMap.clear(); // need for reload case - - // 0 1 2 3 4 5 6 7 8 9 10 11 - QueryResult result = WorldDatabase.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, procFlags, procEx, procPhase, ppmRate, CustomChance, Cooldown FROM spell_proc_event"); - if (!result) + for (uint16 i = 0; i < TOTAL_AURAS; ++i) { - LOG_WARN("server.loading", ">> Loaded 0 spell proc event conditions. DB table `spell_proc_event` is empty."); - return; + isTriggerAura[i] = false; + isAlwaysTriggeredAura[i] = false; } + isTriggerAura[SPELL_AURA_DUMMY] = true; // Most dummy auras should require scripting + isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; // "Any direct damaging attack will revive targets" + isTriggerAura[SPELL_AURA_MOD_THREAT] = true; // Only one spell: 28762 part of Mage T3 8p bonus + isTriggerAura[SPELL_AURA_MOD_STUN] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_DAMAGE_DONE] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_TAKEN] = true; + isTriggerAura[SPELL_AURA_MOD_RESISTANCE] = true; + isTriggerAura[SPELL_AURA_MOD_STEALTH] = true; + isTriggerAura[SPELL_AURA_MOD_FEAR] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_ROOT] = true; + isTriggerAura[SPELL_AURA_TRANSFORM] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS] = true; + isTriggerAura[SPELL_AURA_DAMAGE_IMMUNITY] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_DAMAGE] = true; + isTriggerAura[SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true; + isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true; + isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE] = true; + isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true; + isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_ADD_FLAT_MODIFIER] = true; + isTriggerAura[SPELL_AURA_ADD_PCT_MODIFIER] = true; + isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true; - uint32 count = 0; + isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_FEAR] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STUN] = true; + isAlwaysTriggeredAura[SPELL_AURA_TRANSFORM] = true; + isAlwaysTriggeredAura[SPELL_AURA_SPELL_MAGNET] = true; + isAlwaysTriggeredAura[SPELL_AURA_SCHOOL_ABSORB] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true; - do - { - Field* fields = result->Fetch(); - - int32 spellId = fields[0].Get(); - - bool allRanks = false; - if (spellId < 0) - { - allRanks = true; - spellId = -spellId; - } - - SpellInfo const* spellInfo = GetSpellInfo(spellId); - if (!spellInfo) - { - LOG_ERROR("sql.sql", "Spell {} listed in `spell_proc_event` does not exist", spellId); - continue; - } - - if (allRanks) - { - if (!spellInfo->IsRanked()) - LOG_ERROR("sql.sql", "Spell {} listed in `spell_proc_event` with all ranks, but spell has no ranks.", spellId); - - if (spellInfo->GetFirstRankSpell()->Id != uint32(spellId)) - { - LOG_ERROR("sql.sql", "Spell {} listed in `spell_proc_event` is not first rank of spell.", spellId); - continue; - } - } - - SpellProcEventEntry spellProcEvent; - - spellProcEvent.schoolMask = fields[1].Get(); - spellProcEvent.spellFamilyName = fields[2].Get(); - spellProcEvent.spellFamilyMask[0] = fields[3].Get(); - spellProcEvent.spellFamilyMask[1] = fields[4].Get(); - spellProcEvent.spellFamilyMask[2] = fields[5].Get(); - spellProcEvent.procFlags = fields[6].Get(); - spellProcEvent.procEx = fields[7].Get(); - spellProcEvent.procPhase = fields[8].Get(); - spellProcEvent.ppmRate = fields[9].Get(); - spellProcEvent.customChance = fields[10].Get(); - spellProcEvent.cooldown = fields[11].Get(); - - // PROC_SPELL_PHASE_NONE is by default PROC_SPELL_PHASE_HIT - if (spellProcEvent.procPhase == PROC_SPELL_PHASE_NONE) - { - spellProcEvent.procPhase = PROC_SPELL_PHASE_HIT; - } - - while (spellInfo) - { - if (mSpellProcEventMap.find(spellInfo->Id) != mSpellProcEventMap.end()) - { - LOG_ERROR("sql.sql", "Spell {} listed in `spell_proc_event` already has its first rank in table.", spellInfo->Id); - break; - } - - if (!spellInfo->ProcFlags && !spellProcEvent.procFlags) - LOG_ERROR("sql.sql", "Spell {} listed in `spell_proc_event` probally not triggered spell", spellInfo->Id); - - mSpellProcEventMap[spellInfo->Id] = spellProcEvent; - - if (allRanks) - spellInfo = spellInfo->GetNextRankSpell(); - else - break; - } - - ++count; - } while (result->NextRow()); - - LOG_INFO("server.loading", ">> Loaded {} Extra Spell Proc Event Conditions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); - LOG_INFO("server.loading", " "); + return true; } void SpellMgr::LoadSpellProcs() @@ -2016,8 +1837,8 @@ void SpellMgr::LoadSpellProcs() mSpellProcMap.clear(); // need for reload case - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - QueryResult result = WorldDatabase.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc"); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + QueryResult result = WorldDatabase.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, DisableEffectsMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Spell Proc Conditions And Data. DB table `spell_proc` Is Empty."); @@ -2067,10 +1888,11 @@ void SpellMgr::LoadSpellProcs() baseProcEntry.SpellPhaseMask = fields[8].Get(); baseProcEntry.HitMask = fields[9].Get(); baseProcEntry.AttributesMask = fields[10].Get(); - baseProcEntry.ProcsPerMinute = fields[11].Get(); - baseProcEntry.Chance = fields[12].Get(); - baseProcEntry.Cooldown = Milliseconds(fields[13].Get()); - baseProcEntry.Charges = fields[14].Get(); + baseProcEntry.DisableEffectsMask = fields[11].Get(); + baseProcEntry.ProcsPerMinute = fields[12].Get(); + baseProcEntry.Chance = fields[13].Get(); + baseProcEntry.Cooldown = Milliseconds(fields[14].Get()); + baseProcEntry.Charges = fields[15].Get(); while (spellInfo) { @@ -2104,8 +1926,6 @@ void SpellMgr::LoadSpellProcs() LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} has negative value in `ProcsPerMinute` field", spellId); procEntry.ProcsPerMinute = 0; } - if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0) - LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} doesn't have `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellId); if (procEntry.Charges > 99) { LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} has too big value in `Charges` field", spellId); @@ -2125,8 +1945,30 @@ void SpellMgr::LoadSpellProcs() LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} has `SpellPhaseMask` value defined, but it won't be used for defined `ProcFlags` value", spellId); if (procEntry.HitMask & ~PROC_HIT_MASK_ALL) LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} has wrong `HitMask` set: {}", spellId, procEntry.HitMask); - if (procEntry.HitMask && !(procEntry.ProcFlags & TAKEN_HIT_PROC_FLAG_MASK || (procEntry.ProcFlags & DONE_HIT_PROC_FLAG_MASK && (!procEntry.SpellPhaseMask || procEntry.SpellPhaseMask & (PROC_SPELL_PHASE_HIT | PROC_SPELL_PHASE_FINISH))))) + // HitMask is valid for: TAKEN procs, or DONE procs at any phase (CAST phase has pre-calculated crit for travel-time spells) + if (procEntry.HitMask && !(procEntry.ProcFlags & TAKEN_HIT_PROC_FLAG_MASK || procEntry.ProcFlags & DONE_HIT_PROC_FLAG_MASK)) LOG_ERROR("sql.sql", "`spell_proc` table entry for SpellId {} has `HitMask` value defined, but it won't be used for defined `ProcFlags` and `SpellPhaseMask` values", spellId); + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if ((procEntry.DisableEffectsMask & (1u << i)) && !spellInfo->Effects[i].IsAura()) + LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId {} has DisableEffectsMask with effect {}, but effect {} is not an aura effect", spellId, static_cast(i), static_cast(i)); + if (procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD) + { + bool found = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!spellInfo->Effects[i].IsAura()) + continue; + + if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_ADD_PCT_MODIFIER || spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_ADD_FLAT_MODIFIER) + { + found = true; + break; + } + } + + if (!found) + LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId {} has Attribute PROC_ATTR_REQ_SPELLMOD, but spell has no spell mods. Proc will not be triggered", spellId); + } mSpellProcMap[spellInfo->Id] = procEntry; @@ -2140,6 +1982,104 @@ void SpellMgr::LoadSpellProcs() LOG_INFO("server.loading", ">> Loaded {} spell proc conditions and data in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); + + // Generate default procs for spells with proc flags but no explicit spell_proc entry + // This ensures backward compatibility and covers spells that rely on DBC data + LOG_INFO("server.loading", "Generating spell proc data from SpellMap..."); + count = 0; + oldMSTime = getMSTime(); + + for (SpellInfo const* spellInfo : mSpellInfoMap) + { + if (!spellInfo) + continue; + + // Skip if already has explicit entry + if (mSpellProcMap.find(spellInfo->Id) != mSpellProcMap.end()) + continue; + + // Check if spell has any trigger aura effects + bool found = false, addTriggerFlag = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!spellInfo->Effects[i].IsEffect()) + continue; + + uint32 auraName = spellInfo->Effects[i].ApplyAuraName; + if (!auraName) + continue; + + if (!isTriggerAura[auraName]) + continue; + + found = true; + + if (!addTriggerFlag && isAlwaysTriggeredAura[auraName]) + addTriggerFlag = true; + break; + } + + if (!found) + continue; + + // Skip if no proc flags in DBC + if (!spellInfo->ProcFlags) + continue; + + // Generate default proc entry from DBC data + SpellProcEntry procEntry; + procEntry.SchoolMask = 0; + procEntry.SpellFamilyName = spellInfo->SpellFamilyName; + procEntry.SpellFamilyMask[0] = 0; + procEntry.SpellFamilyMask[1] = 0; + procEntry.SpellFamilyMask[2] = 0; + for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (spellInfo->Effects[i].IsEffect() && isTriggerAura[spellInfo->Effects[i].ApplyAuraName]) + procEntry.SpellFamilyMask |= spellInfo->Effects[i].SpellClassMask; + + procEntry.ProcFlags = spellInfo->ProcFlags; + procEntry.SpellTypeMask = PROC_SPELL_TYPE_MASK_ALL; + procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; + procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent + + // Reflect auras should only proc off reflects + for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->Effects[i].IsAura(SPELL_AURA_REFLECT_SPELLS) || spellInfo->Effects[i].IsAura(SPELL_AURA_REFLECT_SPELLS_SCHOOL)) + { + procEntry.HitMask = PROC_HIT_REFLECT; + break; + } + } + + procEntry.AttributesMask = 0; + if (spellInfo->ProcFlags & PROC_FLAG_KILL) + procEntry.AttributesMask |= PROC_ATTR_REQ_EXP_OR_HONOR; + if (addTriggerFlag) + procEntry.AttributesMask |= PROC_ATTR_TRIGGERED_CAN_PROC; + + // Calculate DisableEffectsMask for effects that shouldn't trigger procs + uint32 nonProcMask = 0; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!spellInfo->Effects[i].IsAura()) + continue; + if (!isTriggerAura[spellInfo->Effects[i].ApplyAuraName]) + nonProcMask |= 1u << i; + } + procEntry.DisableEffectsMask = nonProcMask; + + procEntry.ProcsPerMinute = 0; + procEntry.Chance = static_cast(spellInfo->ProcChance); + procEntry.Cooldown = Milliseconds::zero(); + procEntry.Charges = spellInfo->ProcCharges; + + mSpellProcMap[spellInfo->Id] = procEntry; + ++count; + } + + LOG_INFO("server.loading", ">> Generated spell proc data for {} spells in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); } void SpellMgr::LoadSpellBonuses() @@ -3176,11 +3116,15 @@ void SpellMgr::LoadSpellInfoCustomAttributes() case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC: case SPELL_EFFECT_ENCHANT_HELD_ITEM: { - // only enchanting profession enchantments procs can stack + // Only Enchanting profession enchant procs can stack when dual-wielding + // DK runes (e.g., Unholy Strength) should refresh, not stack if (IsPartOfSkillLine(SKILL_ENCHANTING, i)) { uint32 enchantId = spellInfo->Effects[j].MiscValue; SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId); + if (!enchant) + break; + for (uint8 s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s) { if (enchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index a451404b6..a56ec9145 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -154,21 +154,23 @@ enum ProcFlags | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS | PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS, SPELL_PROC_FLAG_MASK = PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS | PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS + | PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS | PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS - | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, - - SPELL_CAST_PROC_FLAG_MASK = SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION | RANGED_PROC_FLAG_MASK, + | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG + | PROC_FLAG_DONE_PERIODIC | PROC_FLAG_TAKEN_PERIODIC + | PROC_FLAG_DONE_TRAP_ACTIVATION, PERIODIC_PROC_FLAG_MASK = PROC_FLAG_DONE_PERIODIC | PROC_FLAG_TAKEN_PERIODIC, DONE_HIT_PROC_FLAG_MASK = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_RANGED_AUTO_ATTACK - | PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS - | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG - | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG - | PROC_FLAG_DONE_PERIODIC | PROC_FLAG_DONE_MAINHAND_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK, + | PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS + | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG + | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG + | PROC_FLAG_DONE_PERIODIC | PROC_FLAG_DONE_TRAP_ACTIVATION + | PROC_FLAG_DONE_MAINHAND_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK, TAKEN_HIT_PROC_FLAG_MASK = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK | PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK | PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS | PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS @@ -268,24 +270,15 @@ enum ProcFlagsHit enum ProcAttributes { - PROC_ATTR_REQ_EXP_OR_HONOR = 0x0000010, + PROC_ATTR_REQ_EXP_OR_HONOR = 0x0000001, // requires proc target to give exp or honor for aura proc + PROC_ATTR_TRIGGERED_CAN_PROC = 0x0000002, // aura can proc even with triggered spells + PROC_ATTR_REQ_MANA_COST = 0x0000004, // requires triggering spell to have a mana cost for aura proc + PROC_ATTR_REQ_SPELLMOD = 0x0000008, // requires triggering spell to be affected by proccing aura to drop charges + PROC_ATTR_USE_STACKS_FOR_CHARGES = 0x0000010, // consuming proc drops a stack from proccing aura instead of charge + PROC_ATTR_REDUCE_PROC_60 = 0x0000080, // aura should have a reduced chance to proc if level of proc actor > 60 + PROC_ATTR_CANT_PROC_FROM_ITEM_CAST = 0x0000100, // do not allow aura proc if proc is caused by a spell casted by item }; -struct SpellProcEventEntry -{ - uint32 schoolMask; // if nonzero - bit mask for matching proc condition based on spell candidate's school: Fire=2, Mask=1<<(2-1)=2 - uint32 spellFamilyName; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyNamer value - flag96 spellFamilyMask; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyFlags (like auras 107 and 108 do) - uint32 procFlags; // bitmask for matching proc event - uint32 procEx; // proc Extend info (see ProcFlagsEx) - uint32 procPhase; // proc phase (see ProcFlagsSpellPhase) - float ppmRate; // for melee (ranged?) damage spells - proc rate per minute. if zero, falls back to flat chance from Spell.dbc - float customChance; // Owerride chance (in most cases for debug only) - uint32 cooldown; // hidden cooldown used for some spell proc events, applied to _triggered_spell_ -}; - -typedef std::unordered_map SpellProcEventMap; - struct SpellProcEntry { uint32 SchoolMask; // if nonzero - bitmask for matching proc condition based on spell's school @@ -296,6 +289,7 @@ struct SpellProcEntry uint32 SpellPhaseMask; // if nonzero - bitmask for matching phase of a spellcast on which proc occurs, see enum ProcFlagsSpellPhase uint32 HitMask; // if nonzero - bitmask for matching proc condition based on hit result, see enum ProcFlagsHit uint32 AttributesMask; // bitmask, see ProcAttributes + uint32 DisableEffectsMask; // bitmask of effects to disable from triggering proc float ProcsPerMinute; // if nonzero - chance to proc is equal to value * aura caster's weapon speed / 60 float Chance; // if nonzero - owerwrite procChance field for given Spell.dbc entry, defines chance of proc to occur, not used if ProcsPerMinute set Milliseconds Cooldown; // if nonzero - cooldown in secs for aura proc, applied to aura @@ -685,10 +679,6 @@ public: SpellGroupStackRule CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2) const; SpellGroupStackRule GetSpellGroupStackRule(SpellGroup group_id) const; - // Spell proc event table - [[nodiscard]] SpellProcEventEntry const* GetSpellProcEvent(uint32 spellId) const; - bool IsSpellProcEventCanTriggeredBy(SpellInfo const* spellProto, SpellProcEventEntry const* spellProcEvent, uint32 EventProcFlag, ProcEventInfo const& eventInfo, bool active) const; - // Spell proc table [[nodiscard]] SpellProcEntry const* GetSpellProcEntry(uint32 spellId) const; bool CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const; @@ -769,7 +759,6 @@ public: void LoadSpellTargetPositions(); void LoadSpellGroups(); void LoadSpellGroupStackRules(); - void LoadSpellProcEvents(); void LoadSpellProcs(); void LoadSpellBonuses(); void LoadSpellThreats(); @@ -801,7 +790,6 @@ private: SpellGroupSpellMap mSpellGroupSpell; SpellGroupStackMap mSpellGroupStack; SameEffectStackMap mSpellSameEffectStack; - SpellProcEventMap mSpellProcEventMap; SpellProcMap mSpellProcMap; SpellBonusMap mSpellBonusMap; SpellThreatMap mSpellThreatMap; diff --git a/src/server/game/Spells/SpellScript.cpp b/src/server/game/Spells/SpellScript.cpp index d726e2ea8..213ecc7b1 100644 --- a/src/server/game/Spells/SpellScript.cpp +++ b/src/server/game/Spells/SpellScript.cpp @@ -733,6 +733,10 @@ bool AuraScript::_Validate(SpellInfo const* entry) if (!entry->HasEffect(SPELL_EFFECT_APPLY_AURA) && !entry->HasAreaAuraEffect()) LOG_ERROR("spells.scripts", "Spell `{}` of script `{}` does not have apply aura effect - handler bound to hook `DoCheckProc` of AuraScript won't be executed", entry->Id, m_scriptName->c_str()); + for (std::list::iterator itr = DoCheckEffectProc.begin(); itr != DoCheckEffectProc.end(); ++itr) + if (!itr->GetAffectedEffectsMask(entry)) + LOG_ERROR("spells.scripts", "Spell `{}` Effect `{}` of script `{}` did not match dbc effect data - handler bound to hook `DoCheckEffectProc` of AuraScript won't be executed", entry->Id, itr->ToString(), m_scriptName->c_str()); + for (std::list::iterator itr = DoAfterCheckProc.begin(); itr != DoAfterCheckProc.end(); ++itr) if (!entry->HasEffect(SPELL_EFFECT_APPLY_AURA) && !entry->HasAreaAuraEffect()) LOG_ERROR("spells.scripts", "Spell `{}` of script `{}` does not have apply aura effect - handler bound to hook `DoAfterCheckProc` of AuraScript won't be executed", entry->Id, m_scriptName->c_str()); @@ -906,6 +910,17 @@ bool AuraScript::CheckProcHandler::Call(AuraScript* auraScript, ProcEventInfo& e return (auraScript->*_HandlerScript)(eventInfo); } +AuraScript::CheckEffectProcHandler::CheckEffectProcHandler(AuraCheckEffectProcFnType handlerScript, uint8 effIndex, uint16 effName) + : EffectBase(effIndex, effName) +{ + _HandlerScript = handlerScript; +} + +bool AuraScript::CheckEffectProcHandler::Call(AuraScript* auraScript, AuraEffect const* aurEff, ProcEventInfo& eventInfo) +{ + return (auraScript->*_HandlerScript)(aurEff, eventInfo); +} + AuraScript::AfterCheckProcHandler::AfterCheckProcHandler(AuraAfterCheckProcFnType handlerScript) { _HandlerScript = handlerScript; @@ -1037,9 +1052,9 @@ DynamicObject* AuraScript::GetDynobjOwner() const return m_aura->GetDynobjOwner(); } -void AuraScript::Remove(uint32 removeMode) +void AuraScript::Remove(AuraRemoveMode removeMode) { - m_aura->Remove((AuraRemoveMode)removeMode); + m_aura->Remove(removeMode); } Aura* AuraScript::GetAura() const @@ -1177,6 +1192,7 @@ Unit* AuraScript::GetTarget() const case AURA_SCRIPT_HOOK_EFFECT_AFTER_MANASHIELD: case AURA_SCRIPT_HOOK_EFFECT_SPLIT: case AURA_SCRIPT_HOOK_CHECK_PROC: + case AURA_SCRIPT_HOOK_CHECK_EFFECT_PROC: case AURA_SCRIPT_HOOK_AFTER_CHECK_PROC: case AURA_SCRIPT_HOOK_PREPARE_PROC: case AURA_SCRIPT_HOOK_PROC: diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h index 8ab04fd70..0ca94edb2 100644 --- a/src/server/game/Spells/SpellScript.h +++ b/src/server/game/Spells/SpellScript.h @@ -499,6 +499,7 @@ enum AuraScriptHookType AURA_SCRIPT_HOOK_AFTER_DISPEL, // Spell Proc Hooks AURA_SCRIPT_HOOK_CHECK_PROC, + AURA_SCRIPT_HOOK_CHECK_EFFECT_PROC, AURA_SCRIPT_HOOK_AFTER_CHECK_PROC, AURA_SCRIPT_HOOK_PREPARE_PROC, AURA_SCRIPT_HOOK_PROC, @@ -530,6 +531,7 @@ public: typedef void(CLASSNAME::*AuraEffectAbsorbFnType)(AuraEffect*, DamageInfo &, uint32 &); \ typedef void(CLASSNAME::*AuraEffectSplitFnType)(AuraEffect*, DamageInfo &, uint32 &); \ typedef bool(CLASSNAME::*AuraCheckProcFnType)(ProcEventInfo&); \ + typedef bool(CLASSNAME::*AuraCheckEffectProcFnType)(AuraEffect const*, ProcEventInfo&); \ typedef bool(CLASSNAME::*AuraAfterCheckProcFnType)(ProcEventInfo&, bool); \ typedef void(CLASSNAME::*AuraProcFnType)(ProcEventInfo&); \ typedef void(CLASSNAME::*AuraEffectProcFnType)(AuraEffect const*, ProcEventInfo&); \ @@ -640,6 +642,14 @@ public: private: AuraCheckProcFnType _HandlerScript; }; + class CheckEffectProcHandler : public EffectBase + { + public: + CheckEffectProcHandler(AuraCheckEffectProcFnType handlerScript, uint8 effIndex, uint16 effName); + bool Call(AuraScript* auraScript, AuraEffect const* aurEff, ProcEventInfo& eventInfo); + private: + AuraCheckEffectProcFnType _HandlerScript; + }; class AfterCheckProcHandler { public: @@ -678,6 +688,7 @@ public: class EffectManaShieldFunction : public AuraScript::EffectManaShieldHandler { public: EffectManaShieldFunction(AuraEffectAbsorbFnType _pEffectHandlerScript, uint8 _effIndex) : AuraScript::EffectManaShieldHandler((AuraScript::AuraEffectAbsorbFnType)_pEffectHandlerScript, _effIndex) {} }; \ class EffectSplitFunction : public AuraScript::EffectSplitHandler { public: EffectSplitFunction(AuraEffectSplitFnType _pEffectHandlerScript, uint8 _effIndex) : AuraScript::EffectSplitHandler((AuraScript::AuraEffectSplitFnType)_pEffectHandlerScript, _effIndex) {} }; \ class CheckProcHandlerFunction : public AuraScript::CheckProcHandler { public: CheckProcHandlerFunction(AuraCheckProcFnType handlerScript) : AuraScript::CheckProcHandler((AuraScript::AuraCheckProcFnType)handlerScript) {} }; \ + class CheckEffectProcHandlerFunction : public AuraScript::CheckEffectProcHandler { public: CheckEffectProcHandlerFunction(AuraCheckEffectProcFnType handlerScript, uint8 effIndex, uint16 effName) : AuraScript::CheckEffectProcHandler((AuraScript::AuraCheckEffectProcFnType)handlerScript, effIndex, effName) {} }; \ class AfterCheckProcHandlerFunction : public AuraScript::AfterCheckProcHandler { public: AfterCheckProcHandlerFunction(AuraAfterCheckProcFnType handlerScript) : AuraScript::AfterCheckProcHandler((AuraScript::AuraAfterCheckProcFnType)handlerScript) {} }; \ class AuraProcHandlerFunction : public AuraScript::AuraProcHandler { public: AuraProcHandlerFunction(AuraProcFnType handlerScript) : AuraScript::AuraProcHandler((AuraScript::AuraProcFnType)handlerScript) {} }; \ class EffectProcHandlerFunction : public AuraScript::EffectProcHandler { public: EffectProcHandlerFunction(AuraEffectProcFnType effectHandlerScript, uint8 effIndex, uint16 effName) : AuraScript::EffectProcHandler((AuraScript::AuraEffectProcFnType)effectHandlerScript, effIndex, effName) {} }; \ @@ -816,6 +827,13 @@ public: // where function is: bool function (ProcEventInfo& eventInfo); HookList DoCheckProc; #define AuraCheckProcFn(F) CheckProcHandlerFunction(&F) + + // executed when aura effect checks if it can proc the aura + // example: DoCheckEffectProc += AuraCheckEffectProcFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: bool function (AuraEffect const* aurEff, ProcEventInfo& eventInfo); + HookList DoCheckEffectProc; +#define AuraCheckEffectProcFn(F, I, N) CheckEffectProcHandlerFunction(&F, I, N) + // executed when aura checks if it can proc // example: DoAfterCheckProc += AuraCheckProcFn(class::function); // where function is: bool function (ProcEventInfo& eventInfo); @@ -870,7 +888,7 @@ public: DynamicObject* GetDynobjOwner() const; // removes aura with remove mode (see AuraRemoveMode enum) - void Remove(uint32 removeMode = 0); + void Remove(AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT); // returns aura object of script Aura* GetAura() const; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 035dfcb13..5038964b4 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -495,9 +495,6 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Spell Learn Skills..."); sSpellMgr->LoadSpellLearnSkills(); // must be after LoadSpellRanks - LOG_INFO("server.loading", "Loading Spell Proc Event Conditions..."); - sSpellMgr->LoadSpellProcEvents(); - LOG_INFO("server.loading", "Loading Spell Proc Conditions and Data..."); sSpellMgr->LoadSpellProcs(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index 01db0d650..e04795f1d 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -157,7 +157,6 @@ public: { "spell_loot_template", HandleReloadLootTemplatesSpellCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_linked_spell", HandleReloadSpellLinkedSpellCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_pet_auras", HandleReloadSpellPetAurasCommand, SEC_ADMINISTRATOR, Console::Yes }, - { "spell_proc_event", HandleReloadSpellProcEventCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_proc", HandleReloadSpellProcsCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_scripts", HandleReloadSpellScriptsCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_target_position", HandleReloadSpellTargetPositionCommand, SEC_ADMINISTRATOR, Console::Yes }, @@ -301,7 +300,6 @@ public: HandleReloadSpellAreaCommand(handler); HandleReloadSpellGroupsCommand(handler); HandleReloadSpellLinkedSpellCommand(handler); - HandleReloadSpellProcEventCommand(handler); HandleReloadSpellProcsCommand(handler); HandleReloadSpellBonusesCommand(handler); HandleReloadSpellTargetPositionCommand(handler); @@ -901,14 +899,6 @@ public: return true; } - static bool HandleReloadSpellProcEventCommand(ChatHandler* handler) - { - LOG_INFO("server.loading", "Reloading Spell Proc Event conditions..."); - sSpellMgr->LoadSpellProcEvents(); - handler->SendGlobalGMSysMessage("DB table `spell_proc_event` (spell proc trigger requirements) reloaded."); - return true; - } - static bool HandleReloadSpellProcsCommand(ChatHandler* handler) { LOG_INFO("server.loading", "Reloading Spell Proc conditions and data..."); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp index c83ca2043..145495965 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/boss_anetheron.cpp @@ -28,6 +28,7 @@ enum Spells SPELL_SLEEP = 31298, SPELL_INFERNO = 31299, SPELL_VAMPIRIC_AURA = 31317, + SPELL_VAMPIRIC_AURA_HEAL = 31285, SPELL_ENRAGE = 26662, SPELL_INFERNAL_STUN = 31302, SPELL_INFERNAL_IMMOLATION = 31304 @@ -160,8 +161,37 @@ class spell_anetheron_sleep : public SpellScript } }; +// 31317 - Vampiric Aura +class spell_anetheron_vampiric_aura : public AuraScript +{ + PrepareAuraScript(spell_anetheron_vampiric_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_VAMPIRIC_AURA_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* actor = eventInfo.GetActor(); + int32 bp = damageInfo->GetDamage() * 3; + actor->CastCustomSpell(SPELL_VAMPIRIC_AURA_HEAL, SPELLVALUE_BASE_POINT0, bp, actor, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_anetheron_vampiric_aura::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_boss_anetheron() { RegisterHyjalAI(boss_anetheron); RegisterSpellScript(spell_anetheron_sleep); + RegisterSpellScript(spell_anetheron_vampiric_aura); } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp index 7d5f60c2e..ded579444 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp @@ -1342,6 +1342,28 @@ public: } }; +// 72176 - Blood Beast Blood Link +class spell_deathbringer_blood_beast_blood_link : public AuraScript +{ + PrepareAuraScript(spell_deathbringer_blood_beast_blood_link); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_BLOOD_LINK_DUMMY }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActionTarget()->CastCustomSpell(SPELL_BLOOD_LINK_DUMMY, SPELLVALUE_BASE_POINT0, 3, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_deathbringer_blood_beast_blood_link::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_boss_deathbringer_saurfang() { new boss_deathbringer_saurfang(); @@ -1350,6 +1372,7 @@ void AddSC_boss_deathbringer_saurfang() new npc_saurfang_event(); RegisterSpellScript(spell_deathbringer_blood_link_aura); RegisterSpellScript(spell_deathbringer_blood_link_blood_beast_aura); + RegisterSpellScript(spell_deathbringer_blood_beast_blood_link); RegisterSpellScript(spell_deathbringer_blood_link); RegisterSpellAndAuraScriptPair(spell_deathbringer_blood_power, spell_deathbringer_blood_power_aura); RegisterSpellScript(spell_deathbringer_blood_nova_targeting); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp index 009319691..b22267b70 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp @@ -1522,11 +1522,37 @@ class spell_putricide_regurgitated_ooze : public SpellScript } }; +// 71770 - Ooze Tank Protection +class spell_putricide_ooze_tank_protection : public AuraScript +{ + PrepareAuraScript(spell_putricide_ooze_tank_protection); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell, spellInfo->Effects[EFFECT_1].TriggerSpell }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* actionTarget = eventInfo.GetActionTarget(); + actionTarget->CastSpell(nullptr, GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_putricide_ooze_tank_protection::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + OnEffectProc += AuraEffectProcFn(spell_putricide_ooze_tank_protection::HandleProc, EFFECT_1, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_boss_professor_putricide() { new boss_professor_putricide(); new npc_volatile_ooze(); new npc_gas_cloud(); + RegisterSpellScript(spell_putricide_ooze_tank_protection); RegisterSpellScript(spell_putricide_slime_puddle); RegisterSpellScript(spell_putricide_slime_puddle_spawn); RegisterSpellScript(spell_putricide_grow_stacker_aura); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp index 8bb4fded8..80690a7f6 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp @@ -56,6 +56,9 @@ enum XT002Spells SPELL_SPARK_SUMMON = 64210, SPELL_SPARK_DAMAGE = 64227, SPELL_SPARK_MELEE = 64230, + + // ACHIEVEMENT + SPELL_ACHIEVEMENT_CREDIT_NERF_SCRAPBOTS = 65037, }; enum XT002Events @@ -946,6 +949,36 @@ public: } }; +// 65032 - 321 Boombot Aura +class spell_xt002_321_boombot_aura : public AuraScript +{ + PrepareAuraScript(spell_xt002_321_boombot_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_ACHIEVEMENT_CREDIT_NERF_SCRAPBOTS }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (eventInfo.GetActionTarget()->GetEntry() != NPC_XS013_SCRAPBOT) + return false; + return true; + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + if (InstanceScript* instance = eventInfo.GetActor()->GetInstanceScript()) + instance->DoCastSpellOnPlayers(SPELL_ACHIEVEMENT_CREDIT_NERF_SCRAPBOTS); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_xt002_321_boombot_aura::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_xt002_321_boombot_aura::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_boss_xt002() { // Npcs @@ -961,6 +994,7 @@ void AddSC_boss_xt002() RegisterSpellAndAuraScriptPair(spell_xt002_gravity_bomb, spell_xt002_gravity_bomb_aura); RegisterSpellScript(spell_xt002_gravity_bomb_damage); RegisterSpellAndAuraScriptPair(spell_xt002_searing_light_spawn_life_spark, spell_xt002_searing_light_spawn_life_spark_aura); + RegisterSpellScript(spell_xt002_321_boombot_aura); // Achievements new achievement_xt002_nerf_engineering(); diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp index 1a7cb3030..fcad4c62c 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp @@ -203,6 +203,44 @@ enum TickingTimeBomb SPELL_TICKING_TIME_BOMB_EXPLODE = 59687 }; +enum SecondWind +{ + SPELL_SECOND_WIND_TRIGGER = 42771 +}; + +// 42770 - Second Wind +class spell_uk_second_wind : public AuraScript +{ + PrepareAuraScript(spell_uk_second_wind); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SECOND_WIND_TRIGGER }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + return (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN))) != 0; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActionTarget(); + caster->CastSpell(caster, SPELL_SECOND_WIND_TRIGGER, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_uk_second_wind::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_uk_second_wind::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + class spell_ticking_time_bomb_aura : public AuraScript { PrepareAuraScript(spell_ticking_time_bomb_aura); @@ -231,5 +269,6 @@ void AddSC_utgarde_keep() RegisterUtgardeKeepCreatureAI(npc_dragonflayer_forge_master); RegisterUtgardeKeepCreatureAI(npc_enslaved_proto_drake); + RegisterSpellScript(spell_uk_second_wind); RegisterSpellScript(spell_ticking_time_bomb_aura); } diff --git a/src/server/scripts/Outland/boss_doomlord_kazzak.cpp b/src/server/scripts/Outland/boss_doomlord_kazzak.cpp index 5c48392f4..a519499a4 100644 --- a/src/server/scripts/Outland/boss_doomlord_kazzak.cpp +++ b/src/server/scripts/Outland/boss_doomlord_kazzak.cpp @@ -41,9 +41,10 @@ enum Spells SPELL_MARK_OF_KAZZAK = 32960, SPELL_MARK_OF_KAZZAK_DAMAGE = 32961, SPELL_ENRAGE = 32964, - SPELL_CAPTURE_SOUL = 32966, - SPELL_TWISTED_REFLECTION = 21063, - SPELL_BERSERK = 32965 + SPELL_CAPTURE_SOUL = 32966, + SPELL_TWISTED_REFLECTION = 21063, + SPELL_TWISTED_REFLECTION_HEAL = 21064, + SPELL_BERSERK = 32965 }; class boss_doomlord_kazzak : public CreatureScript @@ -184,8 +185,35 @@ class spell_mark_of_kazzak_aura : public AuraScript } }; +// 21063 - Twisted Reflection +class spell_twisted_reflection : public AuraScript +{ + PrepareAuraScript(spell_twisted_reflection); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_TWISTED_REFLECTION_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + eventInfo.GetActionTarget()->CastSpell(eventInfo.GetActor(), SPELL_TWISTED_REFLECTION_HEAL, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_twisted_reflection::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_boss_doomlordkazzak() { new boss_doomlord_kazzak(); RegisterSpellScript(spell_mark_of_kazzak_aura); + RegisterSpellScript(spell_twisted_reflection); } diff --git a/src/server/scripts/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp index b82180e66..377144030 100644 --- a/src/server/scripts/Pet/pet_hunter.cpp +++ b/src/server/scripts/Pet/pet_hunter.cpp @@ -23,6 +23,9 @@ #include "CreatureScript.h" #include "PetDefines.h" #include "ScriptedCreature.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" +#include "SpellScriptLoader.h" enum HunterSpells { @@ -33,6 +36,18 @@ enum HunterSpells SPELL_HUNTER_PET_SCALING = 62915 }; +enum PetSpellsMisc +{ + SPELL_PET_GUARD_DOG_HAPPINESS = 54445, + SPELL_PET_SILVERBACK_RANK_1 = 62800, + SPELL_PET_SILVERBACK_RANK_2 = 62801, + + PET_ICON_ID_GROWL = 201, + PET_ICON_ID_CLAW = 262, + PET_ICON_ID_BITE = 1680, + PET_ICON_ID_SMACK = 473 +}; + struct npc_pet_hunter_snake_trap : public ScriptedAI { npc_pet_hunter_snake_trap(Creature* creature) : ScriptedAI(creature) { _init = false; } @@ -132,7 +147,131 @@ private: uint32 _spellTimer; }; +// -53178 - Guard Dog +class spell_pet_guard_dog : public AuraScript +{ + PrepareAuraScript(spell_pet_guard_dog); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PET_GUARD_DOG_HAPPINESS }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Growl shares family flags with other spells + // filter by spellIcon instead + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || spellInfo->SpellIconID != PET_ICON_ID_GROWL) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + caster->CastSpell(nullptr, SPELL_PET_GUARD_DOG_HAPPINESS, true, nullptr, aurEff); + + Unit* target = eventInfo.GetActionTarget(); + if (!target->CanHaveThreatList()) + return; + + SpellInfo const* procSpellInfo = eventInfo.GetSpellInfo(); + if (!procSpellInfo) + return; + + float addThreat = CalculatePct(static_cast(procSpellInfo->Effects[EFFECT_0].CalcValue(caster)), aurEff->GetAmount()); + target->GetThreatMgr().AddThreat(caster, addThreat, SPELL_SCHOOL_MASK_NORMAL, GetSpellInfo()); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pet_guard_dog::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pet_guard_dog::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -62764 - Silverback +class spell_pet_silverback : public AuraScript +{ + PrepareAuraScript(spell_pet_silverback); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PET_SILVERBACK_RANK_1, SPELL_PET_SILVERBACK_RANK_2 }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Growl shares family flags with other spells + // filter by spellIcon instead + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || spellInfo->SpellIconID != PET_ICON_ID_GROWL) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static uint32 const triggerSpell[2] = { SPELL_PET_SILVERBACK_RANK_1, SPELL_PET_SILVERBACK_RANK_2 }; + + PreventDefaultAction(); + + uint8 rank = GetSpellInfo()->GetRank(); + if (rank > 0 && rank <= 2) + { + uint32 spellId = triggerSpell[rank - 1]; + eventInfo.GetActor()->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pet_silverback::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pet_silverback::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -61680 - Culling the Herd +class spell_pet_culling_the_herd : public AuraScript +{ + PrepareAuraScript(spell_pet_culling_the_herd); + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Claw, Bite and Smack share FamilyFlags with other spells + // filter by spellIcon instead + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + switch (spellInfo->SpellIconID) + { + case PET_ICON_ID_CLAW: + case PET_ICON_ID_BITE: + case PET_ICON_ID_SMACK: + break; + default: + return false; + } + + return true; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pet_culling_the_herd::CheckProc); + } +}; + void AddSC_hunter_pet_scripts() { RegisterCreatureAI(npc_pet_hunter_snake_trap); + RegisterSpellScript(spell_pet_guard_dog); + RegisterSpellScript(spell_pet_silverback); + RegisterSpellScript(spell_pet_culling_the_herd); } diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index b27043aae..7b164e32d 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -81,11 +81,43 @@ enum DeathKnightSpells SPELL_DK_RAISE_ALLY = 46619, SPELL_DK_THRASH = 47480, SPELL_GHOUL_FRENZY = 62218, + // Proc system spells + SPELL_DK_ACCLIMATION_HOLY = 50490, + SPELL_DK_ACCLIMATION_FIRE = 50362, + SPELL_DK_ACCLIMATION_FROST = 50485, + SPELL_DK_ACCLIMATION_ARCANE = 50486, + SPELL_DK_ACCLIMATION_SHADOW = 50489, + SPELL_DK_ACCLIMATION_NATURE = 50488, + SPELL_DK_ADVANTAGE_T10_4P_MELEE = 70657, + SPELL_DK_BUTCHERY_RUNIC_POWER = 50163, + SPELL_DK_MARK_OF_BLOOD_HEAL = 61607, + SPELL_DK_UNHOLY_BLIGHT_DOT = 50536, + SPELL_DK_GLYPH_OF_UNHOLY_BLIGHT = 63332, + SPELL_DK_VENDETTA_HEAL = 50181, + SPELL_DK_NECROSIS_DAMAGE = 51460, + SPELL_DK_RUNIC_RETURN = 61258, + SPELL_DK_DEATH_COIL_R1 = 47541, + SPELL_DK_DEATH_GRIP_INITIAL = 49576, + SPELL_DK_GLYPH_OF_SCOURGE_STRIKE_SCRIPT = 69961, + SPELL_DK_HOWLING_BLAST_R1 = 49184, + SPELL_DK_OBLITERATE_OFF_HAND_R1 = 66198, + SPELL_DK_FROST_STRIKE_OFF_HAND_R1 = 66196, + SPELL_DK_PLAGUE_STRIKE_OFF_HAND_R1 = 66216, + SPELL_DK_DEATH_STRIKE_OFF_HAND_R1 = 66188, + SPELL_DK_RUNE_STRIKE_OFF_HAND_R1 = 66217, + SPELL_DK_BLOOD_STRIKE_OFF_HAND_R1 = 66215, + SPELL_DK_KILLING_MACHINE = 51124, }; enum DeathKnightSpellIcons { - DK_ICON_ID_IMPROVED_DEATH_STRIKE = 2751 + DK_ICON_ID_IMPROVED_DEATH_STRIKE = 2751, + DK_ICON_ID_IMPROVED_BLOOD_PRESENCE = 2636, + DK_ICON_ID_BUTCHERY = 2664, + DK_ICON_ID_NECROSIS = 2709, + DK_ICON_ID_THREAT_OF_THASSARIAN = 2023, + DK_ICON_ID_SUDDEN_DOOM = 1939, + DK_ICON_ID_EPIDEMIC = 234 }; enum Misc @@ -494,10 +526,14 @@ class spell_dk_wandering_plague_aura : public AuraScript } // xinef: prevent default proc with castItem passed, which applies 30 sec cooldown to procing of the aura - void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + void HandleProc(ProcEventInfo& eventInfo) { PreventDefaultAction(); + AuraEffect const* aurEff = GetEffect(EFFECT_0); + if (!aurEff) + return; + eventInfo.GetActor()->AddSpellCooldown(SPELL_DK_WANDERING_PLAGUE_TRIGGER, 0, 1000); eventInfo.GetActor()->CastCustomSpell(SPELL_DK_WANDERING_PLAGUE_TRIGGER, SPELLVALUE_BASE_POINT0, CalculatePct(eventInfo.GetDamageInfo()->GetDamage(), aurEff->GetAmount()), eventInfo.GetActionTarget(), TRIGGERED_FULL_MASK); } @@ -505,7 +541,7 @@ class spell_dk_wandering_plague_aura : public AuraScript void Register() override { DoCheckProc += AuraCheckProcFn(spell_dk_wandering_plague_aura::CheckProc); - OnEffectProc += AuraEffectProcFn(spell_dk_wandering_plague_aura::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + OnProc += AuraProcFn(spell_dk_wandering_plague_aura::HandleProc); } }; @@ -881,6 +917,119 @@ class spell_dk_pet_scaling : public AuraScript } }; +// -49200 - Acclimation +class spell_dk_acclimation : public AuraScript +{ + PrepareAuraScript(spell_dk_acclimation); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_DK_ACCLIMATION_HOLY, + SPELL_DK_ACCLIMATION_FIRE, + SPELL_DK_ACCLIMATION_NATURE, + SPELL_DK_ACCLIMATION_FROST, + SPELL_DK_ACCLIMATION_SHADOW, + SPELL_DK_ACCLIMATION_ARCANE + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (DamageInfo* damageInfo = eventInfo.GetDamageInfo()) + { + switch (GetFirstSchoolInMask(damageInfo->GetSchoolMask())) + { + case SPELL_SCHOOL_HOLY: + case SPELL_SCHOOL_FIRE: + case SPELL_SCHOOL_NATURE: + case SPELL_SCHOOL_FROST: + case SPELL_SCHOOL_SHADOW: + case SPELL_SCHOOL_ARCANE: + return true; + default: + break; + } + } + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + uint32 triggerspell = 0; + + switch (GetFirstSchoolInMask(eventInfo.GetDamageInfo()->GetSchoolMask())) + { + case SPELL_SCHOOL_HOLY: + triggerspell = SPELL_DK_ACCLIMATION_HOLY; + break; + case SPELL_SCHOOL_FIRE: + triggerspell = SPELL_DK_ACCLIMATION_FIRE; + break; + case SPELL_SCHOOL_NATURE: + triggerspell = SPELL_DK_ACCLIMATION_NATURE; + break; + case SPELL_SCHOOL_FROST: + triggerspell = SPELL_DK_ACCLIMATION_FROST; + break; + case SPELL_SCHOOL_SHADOW: + triggerspell = SPELL_DK_ACCLIMATION_SHADOW; + break; + case SPELL_SCHOOL_ARCANE: + triggerspell = SPELL_DK_ACCLIMATION_ARCANE; + break; + default: + return; + } + + if (Unit* target = eventInfo.GetActionTarget()) + target->CastSpell(target, triggerspell, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_acclimation::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dk_acclimation::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 70656 - Advantage T10 4P (DK) +class spell_dk_advantage_t10_4p : public AuraScript +{ + PrepareAuraScript(spell_dk_advantage_t10_4p); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_ADVANTAGE_T10_4P_MELEE }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (Unit* caster = eventInfo.GetActor()) + { + Player* player = caster->ToPlayer(); + if (!player || player->getClass() != CLASS_DEATH_KNIGHT) + return false; + + for (uint8 i = 0; i < MAX_RUNES; ++i) + if (player->GetRuneCooldown(i) == 0) + return false; + + return true; + } + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_advantage_t10_4p::CheckProc); + } +}; + // 50462 - Anti-Magic Zone (on raid member) class spell_dk_anti_magic_shell_raid : public AuraScript { @@ -2314,16 +2463,164 @@ class spell_dk_army_of_the_dead_passive : public AuraScript } }; +// -50163 - Butchery +class spell_dk_butchery : public AuraScript +{ + PrepareAuraScript(spell_dk_butchery); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_BUTCHERY_RUNIC_POWER }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastCustomSpell(SPELL_DK_BUTCHERY_RUNIC_POWER, SPELLVALUE_BASE_POINT0, aurEff->GetAmount(), GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_butchery::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 49005 - Mark of Blood +class spell_dk_mark_of_blood : public AuraScript +{ + PrepareAuraScript(spell_dk_mark_of_blood); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_MARK_OF_BLOOD_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + // Heal the target that the marked enemy attacked (from TrinityCore) + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), SPELL_DK_MARK_OF_BLOOD_HEAL, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_mark_of_blood::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 49194 - Unholy Blight +class spell_dk_unholy_blight : public AuraScript +{ + PrepareAuraScript(spell_dk_unholy_blight); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_UNHOLY_BLIGHT_DOT, SPELL_DK_GLYPH_OF_UNHOLY_BLIGHT }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + return damageInfo && damageInfo->GetDamage(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = GetCaster(); + Unit* target = eventInfo.GetActionTarget(); + if (!caster || !target) + return; + + SpellInfo const* unholyBlight = sSpellMgr->GetSpellInfo(SPELL_DK_UNHOLY_BLIGHT_DOT); + if (!unholyBlight) + return; + + int32 bp = CalculatePct(static_cast(eventInfo.GetDamageInfo()->GetDamage()), aurEff->GetAmount()); + + // Glyph of Unholy Blight + if (AuraEffect* glyph = caster->GetAuraEffect(SPELL_DK_GLYPH_OF_UNHOLY_BLIGHT, EFFECT_0)) + AddPct(bp, glyph->GetAmount()); + + bp = bp / (unholyBlight->GetMaxDuration() / unholyBlight->Effects[EFFECT_0].Amplitude); + target->CastDelayedSpellWithPeriodicAmount(caster, SPELL_DK_UNHOLY_BLIGHT_DOT, SPELL_AURA_PERIODIC_DAMAGE, bp); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_unholy_blight::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dk_unholy_blight::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -50181 - Vendetta +class spell_dk_vendetta : public AuraScript +{ + PrepareAuraScript(spell_dk_vendetta); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_VENDETTA_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + int32 bp = target->CountPctFromMaxHealth(aurEff->GetAmount()); + target->CastCustomSpell(SPELL_DK_VENDETTA_HEAL, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_vendetta::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -51459 - Necrosis +class spell_dk_necrosis : public AuraScript +{ + PrepareAuraScript(spell_dk_necrosis); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_NECROSIS_DAMAGE }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + return damageInfo && damageInfo->GetDamage(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = GetTarget(); + Unit* target = eventInfo.GetActionTarget(); + + int32 bp = CalculatePct(static_cast(eventInfo.GetDamageInfo()->GetDamage()), aurEff->GetAmount()); + caster->CastCustomSpell(SPELL_DK_NECROSIS_DAMAGE, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_necrosis::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dk_necrosis::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + // -49182 Blade Barrier class spell_dk_blade_barrier : public AuraScript { PrepareAuraScript(spell_dk_blade_barrier); - bool CheckProc(ProcEventInfo& /*eventInfo*/) + bool CheckProc(ProcEventInfo& eventInfo) { - if (Player* player = GetCaster()->ToPlayer()) - if (player->getClass() == CLASS_DEATH_KNIGHT && player->IsBaseRuneSlotsOnCooldown(RUNE_BLOOD)) - return true; + if (eventInfo.GetSpellInfo()) + if (Player* player = eventInfo.GetActor()->ToPlayer()) + if (player->getClass() == CLASS_DEATH_KNIGHT && player->IsBaseRuneSlotsOnCooldown(RUNE_BLOOD)) + return true; return false; } @@ -2334,6 +2631,359 @@ class spell_dk_blade_barrier : public AuraScript } }; +// -49208, -49467, -54639 - Death Rune +class spell_dk_death_rune : public AuraScript +{ + PrepareAuraScript(spell_dk_death_rune); + + bool Load() override + { + return GetUnitOwner()->IsPlayer() && GetUnitOwner()->ToPlayer()->getClass() == CLASS_DEATH_KNIGHT; + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* caster = eventInfo.GetActor(); + if (!caster || !caster->IsPlayer()) + return false; + + Player* player = caster->ToPlayer(); + if (player->getClass() != CLASS_DEATH_KNIGHT) + return false; + + return true; + } + + void HandleProc(ProcEventInfo& eventInfo) + { + Player* player = eventInfo.GetActor()->ToPlayer(); + AuraEffect* aurEff = GetEffect(EFFECT_0); + if (!aurEff) + return; + + // Reset amplitude - set death rune remove timer to 30s + aurEff->ResetPeriodic(true); + + uint32 runesLeft = 1; + // Death Rune Mastery (SpellIconID 2622) + if (GetSpellInfo()->SpellIconID == 2622) + runesLeft = 2; + + for (uint8 i = 0; i < MAX_RUNES && runesLeft; ++i) + { + if (GetSpellInfo()->SpellIconID == 2622) + { + if (player->GetBaseRune(i) == RUNE_BLOOD) + continue; + } + else + { + if (player->GetBaseRune(i) != RUNE_BLOOD) + continue; + } + + // Check if rune just went on cooldown + if (player->GetRuneCooldown(i) != player->GetRuneBaseCooldown(i, false)) + continue; + + --runesLeft; + player->AddRuneByAuraEffect(i, RUNE_DEATH, aurEff); + } + } + + void PeriodicTick(AuraEffect const* aurEff) + { + GetTarget()->ToPlayer()->RemoveRunesByAuraEffect(aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_death_rune::CheckProc); + OnProc += AuraProcFn(spell_dk_death_rune::HandleProc); + OnEffectPeriodic += AuraEffectPeriodicFn(spell_dk_death_rune::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// -49188 - Rime +class spell_dk_rime : public AuraScript +{ + PrepareAuraScript(spell_dk_rime); + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + return GetTarget()->IsPlayer(); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + Player* player = GetTarget()->ToPlayer(); + if (!player) + return; + + // Reset cooldown of Howling Blast (all ranks) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_DK_HOWLING_BLAST_R1); + while (spellInfo) + { + player->RemoveSpellCooldown(spellInfo->Id, true); + spellInfo = spellInfo->GetNextRankSpell(); + } + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_rime::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dk_rime::HandleProc, EFFECT_1, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 51124 - Killing Machine +class spell_dk_killing_machine : public AuraScript +{ + PrepareAuraScript(spell_dk_killing_machine); + + void HandleEffectCalcSpellMod(AuraEffect const* /*aurEff*/, SpellModifier*& spellMod) + { + if (spellMod) + { + // Icy Touch (mask0=2), Frost Strike (mask1=4), Howling Blast (mask1=2) + spellMod->mask = flag96(2, 6, 0); + } + } + + void Register() override + { + DoEffectCalcSpellMod += AuraEffectCalcSpellModFn(spell_dk_killing_machine::HandleEffectCalcSpellMod, EFFECT_0, SPELL_AURA_ADD_FLAT_MODIFIER); + } +}; + +// -49018 - Sudden Doom +class spell_dk_sudden_doom : public AuraScript +{ + PrepareAuraScript(spell_dk_sudden_doom); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_DEATH_COIL_R1 }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_DK_DEATH_COIL_R1); + uint32 spellId = 0; + + while (spellInfo) + { + if (!caster->HasSpell(spellInfo->Id)) + break; + + spellId = spellInfo->Id; + spellInfo = spellInfo->GetNextRankSpell(); + } + + if (!spellId) + return; + + caster->CastSpell(eventInfo.GetActionTarget(), spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_sudden_doom::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -65661 - Threat of Thassarian +class spell_dk_threat_of_thassarian : public AuraScript +{ + PrepareAuraScript(spell_dk_threat_of_thassarian); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DK_OBLITERATE_OFF_HAND_R1, + SPELL_DK_FROST_STRIKE_OFF_HAND_R1, + SPELL_DK_PLAGUE_STRIKE_OFF_HAND_R1, + SPELL_DK_DEATH_STRIKE_OFF_HAND_R1, + SPELL_DK_RUNE_STRIKE_OFF_HAND_R1, + SPELL_DK_BLOOD_STRIKE_OFF_HAND_R1 + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + if (!roll_chance_i(aurEff->GetAmount())) + return; + + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + Unit* caster = eventInfo.GetActor(); + if (!caster->haveOffhandWeapon()) + return; + + uint32 spellId = 0; + // Plague Strike + if (spellInfo->SpellFamilyFlags[0] & 0x00000001) + spellId = SPELL_DK_PLAGUE_STRIKE_OFF_HAND_R1; + // Death Strike + else if (spellInfo->SpellFamilyFlags[0] & 0x00000010) + spellId = SPELL_DK_DEATH_STRIKE_OFF_HAND_R1; + // Blood Strike + else if (spellInfo->SpellFamilyFlags[0] & 0x00400000) + spellId = SPELL_DK_BLOOD_STRIKE_OFF_HAND_R1; + // Frost Strike + else if (spellInfo->SpellFamilyFlags[1] & 0x00000004) + spellId = SPELL_DK_FROST_STRIKE_OFF_HAND_R1; + // Obliterate + else if (spellInfo->SpellFamilyFlags[1] & 0x00020000) + spellId = SPELL_DK_OBLITERATE_OFF_HAND_R1; + // Rune Strike + else if (spellInfo->SpellFamilyFlags[1] & 0x20000000) + spellId = SPELL_DK_RUNE_STRIKE_OFF_HAND_R1; + + if (!spellId) + return; + + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + spellId = sSpellMgr->GetSpellWithRank(spellId, spellInfo->GetRank()); + caster->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_threat_of_thassarian::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 62259 - Glyph of Death Grip +class spell_dk_glyph_of_death_grip : public AuraScript +{ + PrepareAuraScript(spell_dk_glyph_of_death_grip); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_DEATH_GRIP_INITIAL }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + if (Player* player = eventInfo.GetActor()->ToPlayer()) + player->RemoveSpellCooldown(SPELL_DK_DEATH_GRIP_INITIAL, true); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_glyph_of_death_grip::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 58642 - Glyph of Scourge Strike +class spell_dk_glyph_of_scourge_strike : public AuraScript +{ + PrepareAuraScript(spell_dk_glyph_of_scourge_strike); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_GLYPH_OF_SCOURGE_STRIKE_SCRIPT }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), SPELL_DK_GLYPH_OF_SCOURGE_STRIKE_SCRIPT, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dk_glyph_of_scourge_strike::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 69961 - Glyph of Scourge Strike (script effect) +class spell_dk_glyph_of_scourge_strike_script : public SpellScript +{ + PrepareSpellScript(spell_dk_glyph_of_scourge_strike_script); + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + Unit* target = GetHitUnit(); + + Unit::AuraEffectList const& mPeriodic = target->GetAuraEffectsByType(SPELL_AURA_PERIODIC_DAMAGE); + for (Unit::AuraEffectList::const_iterator i = mPeriodic.begin(); i != mPeriodic.end(); ++i) + { + AuraEffect const* aurEff = *i; + SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + // Search Blood Plague and Frost Fever on target + if (spellInfo->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && (spellInfo->SpellFamilyFlags[2] & 0x2) && + aurEff->GetCasterGUID() == caster->GetGUID()) + { + uint32 countMin = aurEff->GetBase()->GetMaxDuration(); + uint32 countMax = spellInfo->GetMaxDuration(); + + // this Glyph + countMax += 9000; + // talent Epidemic + if (AuraEffect const* epidemic = caster->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_DEATHKNIGHT, DK_ICON_ID_EPIDEMIC, EFFECT_0)) + countMax += epidemic->GetAmount(); + + if (countMin < countMax) + { + aurEff->GetBase()->SetDuration(aurEff->GetBase()->GetDuration() + 3000); + aurEff->GetBase()->SetMaxDuration(countMin + 3000); + } + } + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_dk_glyph_of_scourge_strike_script::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +// 61257 - PvP 4P Bonus (Runic Power on Snare/Root) +class spell_dk_pvp_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_dk_pvp_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DK_RUNIC_RETURN }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + return (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_SNARE))) != 0; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActionTarget()->CastSpell(nullptr, SPELL_DK_RUNIC_RETURN, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dk_pvp_4p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dk_pvp_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_deathknight_spell_scripts() { RegisterSpellScript(spell_dk_wandering_plague); @@ -2355,6 +3005,8 @@ void AddSC_deathknight_spell_scripts() RegisterSpellScript(spell_dk_dancing_rune_weapon_visual); RegisterSpellScript(spell_dk_scent_of_blood_trigger); RegisterSpellScript(spell_dk_pet_scaling); + RegisterSpellScript(spell_dk_acclimation); + RegisterSpellScript(spell_dk_advantage_t10_4p); RegisterSpellScript(spell_dk_anti_magic_shell_raid); RegisterSpellScript(spell_dk_anti_magic_shell_self); RegisterSpellScript(spell_dk_anti_magic_zone); @@ -2382,5 +3034,20 @@ void AddSC_deathknight_spell_scripts() RegisterSpellScript(spell_dk_will_of_the_necropolis); RegisterSpellScript(spell_dk_ghoul_thrash); RegisterSpellScript(spell_dk_army_of_the_dead_passive); + // Proc system scripts + RegisterSpellScript(spell_dk_butchery); + RegisterSpellScript(spell_dk_mark_of_blood); + RegisterSpellScript(spell_dk_unholy_blight); + RegisterSpellScript(spell_dk_vendetta); + RegisterSpellScript(spell_dk_necrosis); RegisterSpellScript(spell_dk_blade_barrier); + RegisterSpellScript(spell_dk_death_rune); + RegisterSpellScript(spell_dk_rime); + RegisterSpellScript(spell_dk_killing_machine); + RegisterSpellScript(spell_dk_sudden_doom); + RegisterSpellScript(spell_dk_threat_of_thassarian); + RegisterSpellScript(spell_dk_glyph_of_death_grip); + RegisterSpellScript(spell_dk_glyph_of_scourge_strike); + RegisterSpellScript(spell_dk_glyph_of_scourge_strike_script); + RegisterSpellScript(spell_dk_pvp_4p_bonus); } diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index 85a12610d..8a677ff57 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -17,6 +17,7 @@ #include "Containers.h" #include "CreatureScript.h" +#include "GameTime.h" #include "GridNotifiers.h" #include "Player.h" #include "SpellAuraEffects.h" @@ -60,12 +61,52 @@ enum DruidSpells SPELL_DRUID_ENRAGE = 5229, SPELL_DRUID_ENRAGED_DEFENSE = 70725, SPELL_DRUID_ITEM_T10_FERAL_4P_BONUS = 70726, - SPELL_DRUID_MOONGLADE_2P_BONUS = 37286 + SPELL_DRUID_MAIM_INTERRUPT = 32747, + SPELL_DRUID_MOONGLADE_2P_BONUS = 37286, + // Proc system spells + SPELL_DRUID_GLYPH_OF_INNERVATE_MANA = 54833, + SPELL_DRUID_GLYPH_OF_STARFIRE_PROC = 54846, + SPELL_DRUID_GLYPH_OF_RAKE_STUN = 54820, + SPELL_DRUID_LEADER_OF_THE_PACK_HEAL = 34299, + SPELL_DRUID_LEADER_OF_THE_PACK_MANA = 68285, + SPELL_DRUID_GLYPH_OF_REJUV_HEAL = 54755, + SPELL_DRUID_ECLIPSE_LUNAR = 48518, + SPELL_DRUID_ECLIPSE_SOLAR = 48517, + SPELL_DRUID_T3_PROC_ENERGIZE_MANA = 28722, + SPELL_DRUID_T3_PROC_ENERGIZE_RAGE = 28723, + SPELL_DRUID_T3_PROC_ENERGIZE_ENERGY = 28724, + SPELL_DRUID_BLESSING_OF_THE_CLAW = 28750, + SPELL_DRUID_EXHILARATE = 28742, + SPELL_DRUID_INFUSION = 37238, + SPELL_DRUID_BLESSING_OF_REMULOS = 40445, + SPELL_DRUID_BLESSING_OF_ELUNE = 40446, + SPELL_DRUID_BLESSING_OF_CENARIUS = 40452, + SPELL_DRUID_REVITALIZE_ENERGIZE_MANA = 48542, + SPELL_DRUID_REVITALIZE_ENERGIZE_RAGE = 48541, + SPELL_DRUID_REVITALIZE_ENERGIZE_ENERGY = 48540, + SPELL_DRUID_REVITALIZE_ENERGIZE_RP = 48543, + SPELL_DRUID_GLYPH_OF_RIP = 54818, + SPELL_DRUID_RIP_DURATION_LACERATE_DMG = 60141, + SPELL_DRUID_REJUVENATION_T10_PROC = 70691, + SPELL_DRUID_LANGUISH = 71023, + // T9 Feral Relic + SPELL_DRUID_T9_FERAL_RELIC_BEAR = 67354, + SPELL_DRUID_T9_FERAL_RELIC_CAT = 67355, + // Frenzied Regeneration + SPELL_DRUID_FRENZIED_REGENERATION_HEAL = 22845, + // Insect Swarm + SPELL_DRUID_ITEM_T8_BALANCE_RELIC = 64950, + // Nourish + SPELL_DRUID_GLYPH_OF_NOURISH = 62971, + // Wild Growth + SPELL_DRUID_RESTORATION_T10_2P_BONUS = 70658 }; enum DruidIcons { - SPELL_ICON_REVITALIZE = 2862 + SPELL_ICON_REVITALIZE = 2862, + SPELL_ICON_ECLIPSE = 2856, + SPELL_ICON_INNERVATE = 62 }; // 1178 - Bear Form (Passive) @@ -113,27 +154,27 @@ class spell_dru_t10_balance_4p_bonus : public AuraScript { PrepareAuraScript(spell_dru_t10_balance_4p_bonus); - bool CheckProc(ProcEventInfo& eventInfo) + bool Validate(SpellInfo const* /*spellInfo*/) override { - return eventInfo.GetActor() && eventInfo.GetProcTarget(); + return ValidateSpellInfo({ SPELL_DRUID_LANGUISH }); } void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) { PreventDefaultAction(); - uint32 triggered_spell_id = 71023; - SpellInfo const* triggeredSpell = sSpellMgr->GetSpellInfo(triggered_spell_id); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; - int32 amount = CalculatePct(eventInfo.GetDamageInfo()->GetDamage(), aurEff->GetAmount()) / triggeredSpell->GetMaxTicks(); - eventInfo.GetProcTarget()->CastDelayedSpellWithPeriodicAmount(GetTarget(), triggered_spell_id, SPELL_AURA_PERIODIC_DAMAGE, amount, EFFECT_0); + SpellInfo const* triggeredSpell = sSpellMgr->GetSpellInfo(SPELL_DRUID_LANGUISH); - //GetTarget()->CastCustomSpell(triggered_spell_id, SPELLVALUE_BASE_POINT0, amount, eventInfo.GetProcTarget(), true, nullptr, aurEff); + int32 amount = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()) / triggeredSpell->GetMaxTicks(); + eventInfo.GetProcTarget()->CastDelayedSpellWithPeriodicAmount(GetTarget(), SPELL_DRUID_LANGUISH, SPELL_AURA_PERIODIC_DAMAGE, amount, EFFECT_0); } void Register() override { - DoCheckProc += AuraCheckProcFn(spell_dru_t10_balance_4p_bonus::CheckProc); OnEffectProc += AuraEffectProcFn(spell_dru_t10_balance_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } }; @@ -286,21 +327,41 @@ class spell_dru_barkskin : public AuraScript { PrepareAuraScript(spell_dru_barkskin); - void AfterApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + bool Validate(SpellInfo const* /*spellInfo*/) override { - if (GetUnitOwner()->HasAura(SPELL_DRUID_GLYPH_OF_BARKSKIN, GetUnitOwner()->GetGUID())) - GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER, true); + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER }); } - void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { - GetUnitOwner()->RemoveAurasDueToSpell(SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER, GetUnitOwner()->GetGUID()); + GetTarget()->RemoveAurasDueToSpell(SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER); } void Register() override { - AfterEffectApply += AuraEffectApplyFn(spell_dru_barkskin::AfterApply, EFFECT_0, SPELL_AURA_ANY, AURA_EFFECT_HANDLE_REAL); - AfterEffectRemove += AuraEffectRemoveFn(spell_dru_barkskin::AfterRemove, EFFECT_0, SPELL_AURA_ANY, AURA_EFFECT_HANDLE_REAL); + AfterEffectRemove += AuraEffectRemoveFn(spell_dru_barkskin::OnRemove, EFFECT_1, SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 63057 - Glyph of Barkskin +class spell_dru_glyph_of_barkskin : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_barkskin); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActor(), SPELL_DRUID_GLYPH_OF_BARKSKIN_TRIGGER, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_barkskin::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } }; @@ -1265,6 +1326,773 @@ private: ObjectGuid _casterGUID; }; +// 54832 - Glyph of Innervate +class spell_dru_glyph_of_innervate : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_innervate); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_INNERVATE_MANA }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + return spellInfo && spellInfo->SpellIconID == SPELL_ICON_INNERVATE; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* caster = GetTarget(); + int32 manaPercent = aurEff->GetAmount(); + int32 bp = caster->GetCreatePowers(POWER_MANA) * manaPercent / 100 / 10; + caster->CastCustomSpell(SPELL_DRUID_GLYPH_OF_INNERVATE_MANA, SPELLVALUE_BASE_POINT0, bp, caster, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_glyph_of_innervate::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_innervate::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 54821 - Glyph of Rake +class spell_dru_glyph_of_rake : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_rake); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_RAKE_STUN }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // Check if it's Rake (SpellVisual 750 and Effect 1 is periodic damage) + if (spellInfo->SpellVisual[0] != 750 || spellInfo->Effects[EFFECT_1].ApplyAuraName != SPELL_AURA_PERIODIC_DAMAGE) + return false; + + Unit* target = eventInfo.GetActionTarget(); + return target && target->IsCreature(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + GetTarget()->CastSpell(eventInfo.GetActionTarget(), SPELL_DRUID_GLYPH_OF_RAKE_STUN, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_glyph_of_rake::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_rake::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 24932 - Leader of the Pack +class spell_dru_leader_of_the_pack : public AuraScript +{ + PrepareAuraScript(spell_dru_leader_of_the_pack); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_LEADER_OF_THE_PACK_HEAL, SPELL_DRUID_LEADER_OF_THE_PACK_MANA }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + int32 healAmount = aurEff->GetAmount(); + if (healAmount <= 0) + return; + + // 6 second internal cooldown + if (target->IsPlayer() && target->ToPlayer()->HasSpellCooldown(SPELL_DRUID_LEADER_OF_THE_PACK_HEAL)) + return; + + int32 bp = target->CountPctFromMaxHealth(healAmount); + target->CastCustomSpell(SPELL_DRUID_LEADER_OF_THE_PACK_HEAL, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + + if (target->IsPlayer()) + target->ToPlayer()->AddSpellCooldown(SPELL_DRUID_LEADER_OF_THE_PACK_HEAL, 0, 6 * IN_MILLISECONDS); + + // Improved Leader of the Pack - mana regen (only for self-cast aura) + if (aurEff->GetCasterGUID() == target->GetGUID()) + { + int32 manaAmount = CalculatePct(target->GetMaxPower(POWER_MANA), healAmount * 2); + target->CastCustomSpell(SPELL_DRUID_LEADER_OF_THE_PACK_MANA, SPELLVALUE_BASE_POINT0, manaAmount, target, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_leader_of_the_pack::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// 54754 - Glyph of Rejuvenation +class spell_dru_glyph_of_rejuvenation : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_rejuvenation); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_REJUV_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return false; + + // Only proc if target is below health threshold + return target->HealthBelowPct(GetSpellInfo()->Effects[EFFECT_0].CalcValue()); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + int32 bp = CalculatePct(static_cast(eventInfo.GetHealInfo()->GetHeal()), aurEff->GetAmount()); + GetTarget()->CastCustomSpell(SPELL_DRUID_GLYPH_OF_REJUV_HEAL, SPELLVALUE_BASE_POINT0, bp, eventInfo.GetActionTarget(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_glyph_of_rejuvenation::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_rejuvenation::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -48516 - Eclipse +class spell_dru_eclipse : public AuraScript +{ + PrepareAuraScript(spell_dru_eclipse); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_ECLIPSE_LUNAR, SPELL_DRUID_ECLIPSE_SOLAR }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + Unit* target = GetTarget(); + if (!target->IsPlayer()) + return false; + + bool isWrathSpell = (spellInfo->SpellFamilyFlags[0] & 1); + bool isStarfireSpell = (spellInfo->SpellFamilyFlags[0] & 4); + + // Must be Wrath or Starfire + if (!isWrathSpell && !isStarfireSpell) + return false; + + // Check 30 second internal cooldown + uint32 now = GameTime::GetGameTimeMS().count(); + if (isWrathSpell && _lunarProcCooldownEnd > now) + return false; + if (isStarfireSpell && _solarProcCooldownEnd > now) + return false; + + // Don't proc if already have any eclipse aura + if (target->HasAura(SPELL_DRUID_ECLIPSE_LUNAR) || target->HasAura(SPELL_DRUID_ECLIPSE_SOLAR)) + return false; + + // Check proc chance (60% for Wrath, 100% for Starfire) + if (!roll_chance_f(GetSpellInfo()->ProcChance * (isWrathSpell ? 0.6f : 1.0f))) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + bool isWrathSpell = (spellInfo->SpellFamilyFlags[0] & 1); + uint32 triggeredSpell = isWrathSpell ? SPELL_DRUID_ECLIPSE_LUNAR : SPELL_DRUID_ECLIPSE_SOLAR; + + // Set 30 second internal cooldown + uint32 now = GameTime::GetGameTimeMS().count(); + if (isWrathSpell) + _lunarProcCooldownEnd = now + 30000; + else + _solarProcCooldownEnd = now + 30000; + + GetTarget()->CastSpell(GetTarget(), triggeredSpell, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_eclipse::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_eclipse::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } + +private: + uint32 _lunarProcCooldownEnd = 0; + uint32 _solarProcCooldownEnd = 0; +}; + +// -48539 - Revitalize +class spell_dru_revitalize : public AuraScript +{ + PrepareAuraScript(spell_dru_revitalize); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DRUID_REVITALIZE_ENERGIZE_MANA, + SPELL_DRUID_REVITALIZE_ENERGIZE_RAGE, + SPELL_DRUID_REVITALIZE_ENERGIZE_ENERGY, + SPELL_DRUID_REVITALIZE_ENERGIZE_RP + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + if (!roll_chance_i(aurEff->GetAmount())) + return; + + Unit* target = eventInfo.GetActionTarget(); + uint32 spellId; + + switch (target->getPowerType()) + { + case POWER_MANA: + spellId = SPELL_DRUID_REVITALIZE_ENERGIZE_MANA; + break; + case POWER_RAGE: + spellId = SPELL_DRUID_REVITALIZE_ENERGIZE_RAGE; + break; + case POWER_ENERGY: + spellId = SPELL_DRUID_REVITALIZE_ENERGIZE_ENERGY; + break; + case POWER_RUNIC_POWER: + spellId = SPELL_DRUID_REVITALIZE_ENERGIZE_RP; + break; + default: + return; + } + + eventInfo.GetActor()->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_revitalize::HandleProc, EFFECT_0, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + } +}; + +// 28716 - Rejuvenation (T3 2P Bonus) +class spell_dru_t3_2p_bonus : public AuraScript +{ + PrepareAuraScript(spell_dru_t3_2p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DRUID_T3_PROC_ENERGIZE_MANA, + SPELL_DRUID_T3_PROC_ENERGIZE_RAGE, + SPELL_DRUID_T3_PROC_ENERGIZE_ENERGY + }); + } + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + return roll_chance_i(50); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + uint32 spellId; + + switch (target->getPowerType()) + { + case POWER_MANA: + spellId = SPELL_DRUID_T3_PROC_ENERGIZE_MANA; + break; + case POWER_RAGE: + spellId = SPELL_DRUID_T3_PROC_ENERGIZE_RAGE; + break; + case POWER_ENERGY: + spellId = SPELL_DRUID_T3_PROC_ENERGIZE_ENERGY; + break; + default: + return; + } + + eventInfo.GetActor()->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_t3_2p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_t3_2p_bonus::HandleProc, EFFECT_0, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + } +}; + +// 28744 - Regrowth (T3 6P Bonus) +class spell_dru_t3_6p_bonus : public AuraScript +{ + PrepareAuraScript(spell_dru_t3_6p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_BLESSING_OF_THE_CLAW }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), SPELL_DRUID_BLESSING_OF_THE_CLAW, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_t3_6p_bonus::HandleProc, EFFECT_0, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + } +}; + +// 28719 - Healing Touch (T3 8P Bonus) +class spell_dru_t3_8p_bonus : public AuraScript +{ + PrepareAuraScript(spell_dru_t3_8p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_EXHILARATE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + Unit* caster = eventInfo.GetActor(); + int32 amount = CalculatePct(spellInfo->CalcPowerCost(caster, spellInfo->GetSchoolMask()), aurEff->GetAmount()); + caster->CastCustomSpell(SPELL_DRUID_EXHILARATE, SPELLVALUE_BASE_POINT0, amount, caster, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_t3_8p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 37288, 37295 - Mana Restore (T4 2P Bonus) +class spell_dru_t4_2p_bonus : public AuraScript +{ + PrepareAuraScript(spell_dru_t4_2p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_INFUSION }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(nullptr, SPELL_DRUID_INFUSION, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_t4_2p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 40442 - Druid Tier 6 Trinket +class spell_dru_item_t6_trinket : public AuraScript +{ + PrepareAuraScript(spell_dru_item_t6_trinket); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DRUID_BLESSING_OF_REMULOS, + SPELL_DRUID_BLESSING_OF_ELUNE, + SPELL_DRUID_BLESSING_OF_CENARIUS + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + uint32 spellId; + int32 chance; + + // Starfire + if (spellInfo->SpellFamilyFlags[0] & 0x00000004) + { + spellId = SPELL_DRUID_BLESSING_OF_REMULOS; + chance = 25; + } + // Rejuvenation + else if (spellInfo->SpellFamilyFlags[0] & 0x00000010) + { + spellId = SPELL_DRUID_BLESSING_OF_ELUNE; + chance = 25; + } + // Mangle (Bear) and Mangle (Cat) + else if (spellInfo->SpellFamilyFlags[1] & 0x00000440) + { + spellId = SPELL_DRUID_BLESSING_OF_CENARIUS; + chance = 40; + } + else + return; + + if (roll_chance_i(chance)) + eventInfo.GetActor()->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_item_t6_trinket::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 54815 - Glyph of Shred +class spell_dru_glyph_of_shred : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_shred); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DRUID_GLYPH_OF_RIP, + SPELL_DRUID_RIP_DURATION_LACERATE_DMG + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + // Try to find Rip on the target + if (AuraEffect const* rip = eventInfo.GetActionTarget()->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x00800000, 0x0, 0x0, caster->GetGUID())) + { + // Rip's max duration, includes modifiers like Glyph of Rip + uint32 countMin = rip->GetBase()->GetMaxDuration(); + + // Just Rip's max duration without other spells + uint32 countMax = rip->GetSpellInfo()->GetMaxDuration(); + + // Add possible auras' and Glyph of Shred's max duration + countMax += 3 * aurEff->GetAmount() * IN_MILLISECONDS; // Glyph of Shred -> +6 seconds + countMax += caster->HasAura(SPELL_DRUID_GLYPH_OF_RIP) ? 4 * IN_MILLISECONDS : 0; // Glyph of Rip -> +4 seconds + countMax += caster->HasAura(SPELL_DRUID_RIP_DURATION_LACERATE_DMG) ? 4 * IN_MILLISECONDS : 0; // T7 set bonus -> +4 seconds + + // If min < max that means caster didn't cast 3 shred yet + if (countMin < countMax) + { + rip->GetBase()->SetDuration(rip->GetBase()->GetDuration() + aurEff->GetAmount() * IN_MILLISECONDS); + rip->GetBase()->SetMaxDuration(countMin + aurEff->GetAmount() * IN_MILLISECONDS); + } + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_shred::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 54845 - Glyph of Starfire (Dummy) +class spell_dru_glyph_of_starfire_dummy : public AuraScript +{ + PrepareAuraScript(spell_dru_glyph_of_starfire_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_GLYPH_OF_STARFIRE_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), SPELL_DRUID_GLYPH_OF_STARFIRE_PROC, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_glyph_of_starfire_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 70664 - Item - Druid T10 Restoration 4P Bonus (Rejuvenation) +class spell_dru_t10_restoration_4p_bonus_dummy : public AuraScript +{ + PrepareAuraScript(spell_dru_t10_restoration_4p_bonus_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_REJUVENATION_T10_PROC }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || spellInfo->Id == SPELL_DRUID_REJUVENATION_T10_PROC) + return false; + + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return false; + + Player* caster = eventInfo.GetActor()->ToPlayer(); + if (!caster) + return false; + + return caster->GetGroup() || caster != eventInfo.GetActionTarget(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + int32 amount = eventInfo.GetHealInfo()->GetHeal(); + eventInfo.GetActor()->CastCustomSpell(SPELL_DRUID_REJUVENATION_T10_PROC, SPELLVALUE_BASE_POINT0, amount, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_t10_restoration_4p_bonus_dummy::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_t10_restoration_4p_bonus_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 44835 - Maim Interrupt +class spell_dru_maim_interrupt : public AuraScript +{ + PrepareAuraScript(spell_dru_maim_interrupt); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_MAIM_INTERRUPT }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_DRUID_MAIM_INTERRUPT, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_dru_maim_interrupt::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 67353 - T9 Feral Relic (Idol of Mutilation) +class spell_dru_t9_feral_relic : public AuraScript +{ + PrepareAuraScript(spell_dru_t9_feral_relic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DRUID_T9_FERAL_RELIC_BEAR, + SPELL_DRUID_T9_FERAL_RELIC_CAT + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* target = eventInfo.GetActor(); + + switch (target->GetShapeshiftForm()) + { + case FORM_BEAR: + case FORM_DIREBEAR: + case FORM_CAT: + return true; + default: + break; + } + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + uint32 triggerspell = 0; + + Unit* target = eventInfo.GetActor(); + + switch (target->GetShapeshiftForm()) + { + case FORM_BEAR: + case FORM_DIREBEAR: + triggerspell = SPELL_DRUID_T9_FERAL_RELIC_BEAR; + break; + case FORM_CAT: + triggerspell = SPELL_DRUID_T9_FERAL_RELIC_CAT; + break; + default: + return; + } + + target->CastSpell(target, triggerspell, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_dru_t9_feral_relic::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_dru_t9_feral_relic::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 22842 - Frenzied Regeneration +class spell_dru_frenzied_regeneration : public AuraScript +{ + PrepareAuraScript(spell_dru_frenzied_regeneration); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_FRENZIED_REGENERATION_HEAL }); + } + + void HandlePeriodic(AuraEffect const* aurEff) + { + Unit* target = GetTarget(); + if (target->getPowerType() != POWER_RAGE) + return; + + uint32 rage = target->GetPower(POWER_RAGE); + if (!rage) + return; + + int32 const mod = std::min(static_cast(rage), 100); + int32 const points = GetSpellInfo()->Effects[EFFECT_1].CalcValue(target); + int32 const regen = CalculatePct(target->GetMaxHealth(), points * mod / 100.f); + target->CastCustomSpell(SPELL_DRUID_FRENZIED_REGENERATION_HEAL, SPELLVALUE_BASE_POINT0, regen, target, true, nullptr, aurEff); + target->SetPower(POWER_RAGE, rage - mod); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_dru_frenzied_regeneration::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// -5570 - Insect Swarm +class spell_dru_insect_swarm : public AuraScript +{ + PrepareAuraScript(spell_dru_insect_swarm); + + void CalculateAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/) + { + if (Unit* caster = GetCaster()) + if (AuraEffect const* relicAurEff = caster->GetAuraEffect(SPELL_DRUID_ITEM_T8_BALANCE_RELIC, EFFECT_0)) + amount += relicAurEff->GetAmount() / aurEff->GetTotalTicks(); + } + + void Register() override + { + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_dru_insect_swarm::CalculateAmount, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE); + } +}; + +// 50464 - Nourish +class spell_dru_nourish : public SpellScript +{ + PrepareSpellScript(spell_dru_nourish); + + void HandleHeal(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + Unit* target = GetHitUnit(); + if (!target) + return; + + int32 heal = GetHitHeal(); + + // Glyph of Nourish + if (AuraEffect const* aurEff = caster->GetAuraEffect(SPELL_DRUID_GLYPH_OF_NOURISH, EFFECT_0)) + { + uint32 auraCount = 0; + + Unit::AuraEffectList const& periodicHeals = target->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); + for (AuraEffect const* hot : periodicHeals) + { + if (caster->GetGUID() == hot->GetCasterGUID()) + ++auraCount; + } + + AddPct(heal, aurEff->GetAmount() * auraCount); + } + + SetHitHeal(heal); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_dru_nourish::HandleHeal, EFFECT_0, SPELL_EFFECT_HEAL); + } +}; + +// -48438 - Wild Growth (AuraScript) +class spell_dru_wild_growth_aura : public AuraScript +{ + PrepareAuraScript(spell_dru_wild_growth_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DRUID_RESTORATION_T10_2P_BONUS }); + } + + void SetTickHeal(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/) + { + _baseTick = amount; + if (Unit* caster = GetCaster()) + if (AuraEffect const* bonus = caster->GetAuraEffect(SPELL_DRUID_RESTORATION_T10_2P_BONUS, EFFECT_0)) + AddPct(_baseReduction, -bonus->GetAmount()); + } + + void HandleTickUpdate(AuraEffect* aurEff) + { + // Wild Growth = first tick gains a 6% bonus, reduced by 2% each tick + float reduction = _baseReduction; + reduction *= (aurEff->GetTickNumber() - 1); + + float const bonus = 6.f - reduction; + int32 const amount = int32(_baseTick + CalculatePct(_baseTick, bonus)); + aurEff->SetAmount(amount); + } + + void Register() override + { + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_dru_wild_growth_aura::SetTickHeal, EFFECT_0, SPELL_AURA_PERIODIC_HEAL); + OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_dru_wild_growth_aura::HandleTickUpdate, EFFECT_0, SPELL_AURA_PERIODIC_HEAL); + } + + float _baseTick = 0.f; + float _baseReduction = 2.f; +}; + void AddSC_druid_spell_scripts() { RegisterSpellScript(spell_dru_bear_form_passive); @@ -1274,6 +2102,7 @@ void AddSC_druid_spell_scripts() RegisterSpellScript(spell_dru_omen_of_clarity); RegisterSpellScript(spell_dru_brambles_treant); RegisterSpellScript(spell_dru_barkskin); + RegisterSpellScript(spell_dru_glyph_of_barkskin); RegisterSpellScript(spell_dru_treant_scaling); RegisterSpellScript(spell_dru_berserk); RegisterSpellAndAuraScriptPair(spell_dru_dash, spell_dru_dash_aura); @@ -1298,7 +2127,27 @@ void AddSC_druid_spell_scripts() RegisterSpellScript(spell_dru_tiger_s_fury); RegisterSpellScript(spell_dru_typhoon); RegisterSpellScript(spell_dru_t10_restoration_4p_bonus); - RegisterSpellScript(spell_dru_wild_growth); + RegisterSpellAndAuraScriptPair(spell_dru_wild_growth, spell_dru_wild_growth_aura); RegisterSpellScript(spell_dru_moonkin_form_passive_proc); RegisterSpellScript(spell_dru_rejuvenation_moonglade_2_set); + // Proc system scripts + RegisterSpellScript(spell_dru_glyph_of_innervate); + RegisterSpellScript(spell_dru_glyph_of_rake); + RegisterSpellScript(spell_dru_leader_of_the_pack); + RegisterSpellScript(spell_dru_glyph_of_rejuvenation); + RegisterSpellScript(spell_dru_eclipse); + RegisterSpellScript(spell_dru_revitalize); + RegisterSpellScript(spell_dru_t3_2p_bonus); + RegisterSpellScript(spell_dru_t3_6p_bonus); + RegisterSpellScript(spell_dru_t3_8p_bonus); + RegisterSpellScript(spell_dru_t4_2p_bonus); + RegisterSpellScript(spell_dru_item_t6_trinket); + RegisterSpellScript(spell_dru_glyph_of_shred); + RegisterSpellScript(spell_dru_glyph_of_starfire_dummy); + RegisterSpellScript(spell_dru_t10_restoration_4p_bonus_dummy); + RegisterSpellScript(spell_dru_maim_interrupt); + RegisterSpellScript(spell_dru_t9_feral_relic); + RegisterSpellScript(spell_dru_frenzied_regeneration); + RegisterSpellScript(spell_dru_insect_swarm); + RegisterSpellScript(spell_dru_nourish); } diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 55186ba5e..b81ec36f1 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -69,6 +69,89 @@ class spell_gen_5000_gold : public SpellScript } }; +// 430, 431, 432, 1133, 1135, 1137, 10250, 22734, 27089, 34291, 43182, 43183, 46755, 49472, 57073, 61830, 72623 - Drink +class spell_gen_arena_drink : public AuraScript +{ + PrepareAuraScript(spell_gen_arena_drink); + + bool Load() override + { + return GetCaster() && GetCaster()->IsPlayer(); + } + + bool Validate(SpellInfo const* spellInfo) override + { + if (spellInfo->Effects[EFFECT_0].ApplyAuraName != SPELL_AURA_MOD_POWER_REGEN) + { + LOG_ERROR("spells", "Aura {} structure has been changed - first aura is no longer SPELL_AURA_MOD_POWER_REGEN", spellInfo->Id); + return false; + } + + return true; + } + + void CalcPeriodic(AuraEffect const* /*aurEff*/, bool& isPeriodic, int32& /*amplitude*/) + { + AuraEffect* regen = GetAura()->GetEffect(EFFECT_0); + if (!regen) + return; + + // default case - not in arena + if (!GetCaster()->ToPlayer()->InArena()) + isPeriodic = false; + } + + void CalcAmount(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/) + { + AuraEffect* regen = GetAura()->GetEffect(EFFECT_0); + if (!regen) + return; + + // default case - not in arena + if (!GetCaster()->ToPlayer()->InArena()) + regen->ChangeAmount(amount); + } + + void UpdatePeriodic(AuraEffect* aurEff) + { + AuraEffect* regen = GetAura()->GetEffect(EFFECT_0); + if (!regen) + return; + + // This feature used only in arenas + // Here need increase mana regen per tick (6 second rule) + // on 0 tick - 0 (handled in 2 second) + // on 1 tick - 166% (handled in 4 second) + // on 2 tick - 133% (handled in 6 second) + + // Apply bonus for 1 - 4 tick + switch (aurEff->GetTickNumber()) + { + case 1: // 0% + regen->ChangeAmount(0); + break; + case 2: // 166% + regen->ChangeAmount(aurEff->GetAmount() * 5 / 3); + break; + case 3: // 133% + regen->ChangeAmount(aurEff->GetAmount() * 4 / 3); + break; + default: // 100% - normal regen + regen->ChangeAmount(aurEff->GetAmount()); + // No need to update after 4th tick + aurEff->SetPeriodic(false); + break; + } + } + + void Register() override + { + DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(spell_gen_arena_drink::CalcPeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DUMMY); + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_arena_drink::CalcAmount, EFFECT_1, SPELL_AURA_PERIODIC_DUMMY); + OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_gen_arena_drink::UpdatePeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DUMMY); + } +}; + // 24401 - Test Pet Passive class spell_gen_model_visible : public AuraScript { @@ -5696,6 +5779,307 @@ class spell_gen_whisper_to_controller : public SpellScript } }; +enum VampiricTouchSpells +{ + SPELL_VAMPIRIC_TOUCH_HEAL = 52724 +}; + +// 52723 - Vampiric Touch (proc) +class spell_gen_vampiric_touch : public AuraScript +{ + PrepareAuraScript(spell_gen_vampiric_touch); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_VAMPIRIC_TOUCH_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* caster = eventInfo.GetActor(); + int32 bp = damageInfo->GetDamage() / 2; + caster->CastCustomSpell(SPELL_VAMPIRIC_TOUCH_HEAL, SPELLVALUE_BASE_POINT0, bp, caster, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_gen_vampiric_touch::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 62337, 62933 - Petrified Bark (Freya) +class spell_gen_petrified_bark : public AuraScript +{ + PrepareAuraScript(spell_gen_petrified_bark); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 62379 }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* victim = eventInfo.GetActor(); + if (!victim) + return; + + int32 damage = damageInfo->GetDamage(); + victim->CastCustomSpell(GetTarget(), 62379, &damage, nullptr, nullptr, true); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_gen_petrified_bark::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 67534 - Earth Shield (Trial of the Champion) +class spell_gen_earth_shield_toc : public AuraScript +{ + PrepareAuraScript(spell_gen_earth_shield_toc); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 67535 }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + int32 damage = damageInfo->GetDamage(); + GetTarget()->CastCustomSpell(GetTarget(), 67535, &damage, nullptr, nullptr, true, nullptr, aurEff, GetCasterGUID()); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_gen_earth_shield_toc::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 65932 - Retaliation (Faction Champions) +class spell_gen_retaliation_toc : public AuraScript +{ + PrepareAuraScript(spell_gen_retaliation_toc); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 65934 }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* attacker = eventInfo.GetActor(); + return attacker && GetTarget()->HasInArc(M_PI, attacker); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* attacker = eventInfo.GetActor(); + if (attacker) + GetTarget()->CastSpell(attacker, 65934, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_gen_retaliation_toc::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_gen_retaliation_toc::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 69172 - Overlord's Brand (damage/heal redirect - non-DoT) +class spell_gen_overlords_brand : public AuraScript +{ + PrepareAuraScript(spell_gen_overlords_brand); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 69189, 69190 }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = GetCaster(); + if (!caster) + return; + + if (eventInfo.GetTypeMask() & (PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS)) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + int32 heal = static_cast(healInfo->GetHeal() * 5.5f); + GetTarget()->CastCustomSpell(caster, 69190, &heal, nullptr, nullptr, true); + } + else + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + if (Unit* victim = caster->GetVictim()) + { + int32 damage = damageInfo->GetDamage(); + GetTarget()->CastCustomSpell(victim, 69189, &damage, nullptr, nullptr, true); + } + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_gen_overlords_brand::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 69173 - Overlord's Brand (damage/heal redirect - DoT only) +class spell_gen_overlords_brand_dot : public AuraScript +{ + PrepareAuraScript(spell_gen_overlords_brand_dot); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 69189, 69190 }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = GetCaster(); + if (!caster) + return; + + if (eventInfo.GetHitMask() & PROC_EX_INTERNAL_HOT) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + int32 heal = static_cast(healInfo->GetHeal() * 5.5f); + GetTarget()->CastCustomSpell(caster, 69190, &heal, nullptr, nullptr, true); + } + else + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + if (Unit* victim = caster->GetVictim()) + { + int32 damage = damageInfo->GetDamage(); + GetTarget()->CastCustomSpell(victim, 69189, &damage, nullptr, nullptr, true); + } + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_gen_overlords_brand_dot::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 70674 - Vampiric Might (Lady Deathwhisper) +class spell_gen_vampiric_might : public AuraScript +{ + PrepareAuraScript(spell_gen_vampiric_might); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 70677 }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = GetCaster(); + if (!caster) + return; + + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + int32 heal = damageInfo->GetDamage() * 3; + caster->CastCustomSpell(caster, 70677, &heal, nullptr, nullptr, true); + } + + void Register() override + { + OnProc += AuraProcFn(spell_gen_vampiric_might::HandleProc); + } +}; + +// 69023 - Mirrored Soul (Devourer of Souls) +class spell_gen_mirrored_soul : public AuraScript +{ + PrepareAuraScript(spell_gen_mirrored_soul); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 69034 }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* caster = GetCaster(); + if (!caster) + return; + + int32 damage = static_cast(damageInfo->GetDamage() * 0.45f); + if (damage > 0) + GetTarget()->CastCustomSpell(caster, 69034, &damage, nullptr, nullptr, true); + } + + void Register() override + { + OnProc += AuraProcFn(spell_gen_mirrored_soul::HandleProc); + } +}; + +// 27522, 40336, 46939 - Black Bow of the Betrayer / Mana Drain Trigger +class spell_gen_black_bow_of_the_betrayer : public AuraScript +{ + PrepareAuraScript(spell_gen_black_bow_of_the_betrayer); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ 29471, 27526 }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + Unit* victim = eventInfo.GetActionTarget(); + + if (target->IsAlive()) + target->CastSpell(target, 29471, true); + if (victim && victim->IsAlive()) + target->CastSpell(victim, 27526, true); + } + + void Register() override + { + OnProc += AuraProcFn(spell_gen_black_bow_of_the_betrayer::HandleProc); + } +}; + // 35475 Drums of War // 35476 Drums of Battle // 35478 Drums of Restoration @@ -5722,6 +6106,7 @@ void AddSC_generic_spell_scripts() { RegisterSpellScript(spell_silithyst); RegisterSpellScript(spell_gen_5000_gold); + RegisterSpellScript(spell_gen_arena_drink); RegisterSpellScript(spell_gen_model_visible); RegisterSpellScript(spell_the_flag_of_ownership); RegisterSpellScript(spell_gen_have_item_auras); @@ -5893,5 +6278,15 @@ void AddSC_generic_spell_scripts() RegisterSpellScript(spell_gen_bm_on); RegisterSpellScript(spell_gen_bm_off); RegisterSpellScript(spell_gen_whisper_to_controller); + RegisterSpellScript(spell_gen_vampiric_touch); + // Boss and item proc scripts + RegisterSpellScript(spell_gen_petrified_bark); + RegisterSpellScript(spell_gen_earth_shield_toc); + RegisterSpellScript(spell_gen_retaliation_toc); + RegisterSpellScript(spell_gen_overlords_brand); + RegisterSpellScript(spell_gen_overlords_brand_dot); + RegisterSpellScript(spell_gen_vampiric_might); + RegisterSpellScript(spell_gen_mirrored_soul); + RegisterSpellScript(spell_gen_black_bow_of_the_betrayer); RegisterSpellScript(spell_gen_filter_party_level_80); } diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index 7ebf33999..c116241a8 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -68,6 +68,25 @@ enum HunterSpells SPELL_LOCK_AND_LOAD_TRIGGER = 56453, SPELL_LOCK_AND_LOAD_MARKER = 67544, SPELL_HUNTER_PET_LEGGINGS_OF_BEAST_MASTERY = 38297, // Leggings of Beast Mastery + + // Proc system spells + SPELL_HUNTER_THRILL_OF_THE_HUNT_MANA = 34720, + SPELL_HUNTER_REPLENISHMENT = 57669, + SPELL_HUNTER_RAPID_RECUPERATION_R1 = 56654, + SPELL_HUNTER_RAPID_RECUPERATION_R2 = 58882, + SPELL_HUNTER_GLYPH_OF_MEND_PET_HAPPINESS = 57894, + SPELL_HUNTER_KILL_COMMAND_HUNTER = 34026, + SPELL_HUNTER_RAPID_RECUPERATION_MANA_R1 = 56654, + SPELL_HUNTER_RAPID_RECUPERATION_MANA_R2 = 58882, + SPELL_HUNTER_PIERCING_SHOTS = 63468, + SPELL_HUNTER_T9_4P_GREATNESS = 68130 +}; + +enum HunterSpellIcons +{ + HUNTER_ICON_THRILL_OF_THE_HUNT = 2236, + HUNTER_ICON_HUNTING_PARTY = 3406, + HUNTER_ICON_RAPID_RECUPERATION = 3560 }; class spell_hun_check_pet_los : public SpellScript @@ -738,18 +757,15 @@ class spell_hun_sniper_training : public AuraScript PreventDefaultAction(); if (aurEff->GetAmount() <= 0) { - if (!GetCaster() || !GetTarget()) - { - return; - } - Unit* target = GetTarget(); - uint32 spellId = SPELL_HUNTER_SNIPER_TRAINING_BUFF_R1 + GetId() - SPELL_HUNTER_SNIPER_TRAINING_R1; - if (SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(spellId)) + target->CastSpell(target, spellId, true, nullptr, aurEff); + + if (Player* playerTarget = GetUnitOwner()->ToPlayer()) { - Unit* triggerCaster = triggeredSpellInfo->NeedsToBeTriggeredByCaster(GetSpellInfo()) ? GetCaster() : target; - triggerCaster->CastSpell(target, triggeredSpellInfo, true, 0, aurEff); + int32 baseAmount = aurEff->GetBaseAmount(); + int32 amount = playerTarget->CalculateSpellDamage(playerTarget, GetSpellInfo(), aurEff->GetEffIndex(), &baseAmount); + GetEffect(EFFECT_0)->SetAmount(amount); } } } @@ -759,7 +775,7 @@ class spell_hun_sniper_training : public AuraScript if (Player* playerTarget = GetUnitOwner()->ToPlayer()) { int32 baseAmount = aurEff->GetBaseAmount(); - int32 amount = playerTarget->isMoving() || aurEff->GetAmount() <= 0 ? + int32 amount = playerTarget->isMoving() ? playerTarget->CalculateSpellDamage(playerTarget, GetSpellInfo(), aurEff->GetEffIndex(), &baseAmount) : aurEff->GetAmount() - 1; aurEff->SetAmount(amount); @@ -1151,11 +1167,6 @@ private: WorldObject* _target = nullptr; }; -enum LocknLoadSpells -{ - SPELL_FROST_TRAP_SLOW = 67035 -}; - // -56342 - Lock and Load class spell_hun_lock_and_load : public AuraScript { @@ -1163,103 +1174,58 @@ class spell_hun_lock_and_load : public AuraScript bool Validate(SpellInfo const* /*spellInfo*/) override { - return ValidateSpellInfo({ SPELL_LOCK_AND_LOAD_TRIGGER, SPELL_LOCK_AND_LOAD_MARKER, SPELL_FROST_TRAP_SLOW }); + return ValidateSpellInfo( + { + SPELL_LOCK_AND_LOAD_TRIGGER, + SPELL_LOCK_AND_LOAD_MARKER + }); } - bool CheckTrapProc(ProcEventInfo& eventInfo) + bool CheckProc(ProcEventInfo& eventInfo) { - SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); - if (!spellInfo || !eventInfo.GetActor()) - { + if (eventInfo.GetActor()->HasAura(SPELL_LOCK_AND_LOAD_MARKER)) return false; - } - - // Black Arrow and Fire traps may trigger on periodic tick only. - if (((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) - && (spellInfo->Effects[0].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) - { - return true; - } - - return IsTargetValid(spellInfo, eventInfo.GetProcTarget()) && !eventInfo.GetActor()->HasAura(SPELL_LOCK_AND_LOAD_MARKER); - } - - bool IsTargetValid(SpellInfo const* spellInfo, Unit* target) - { - if (!spellInfo || !target) - { - return false; - } - - // Don't check it for fire traps and black arrow, they proc on periodic only and not spell hit. - // So it's wrong to check for immunity, it was already checked when the spell was applied. - if ((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) - { - return false; - } - - // HitMask for Frost Trap can't be checked correctly as it is. - // That's because the talent is triggered by the spell that fires the trap (63487)... - // ...and not the actual spell that applies the slow effect (67035). - // So the IMMUNE result is never sent by the spell that triggers this. - if (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_NATURE) - { - if (SpellInfo const* triggerSpell = sSpellMgr->GetSpellInfo(SPELL_FROST_TRAP_SLOW)) - { - return !target->IsImmunedToSpell(triggerSpell); - } - } - return true; } - template - void HandleProcs(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + bool CheckTrapProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + if (!(eventInfo.GetTypeMask() & PROC_FLAG_DONE_TRAP_ACTIVATION)) + return false; + + // Do not proc on traps for immolation/explosive trap + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || !(spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FROST)) + return false; + + return roll_chance_i(aurEff->GetAmount()); + } + + bool CheckPeriodicProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + if (!(eventInfo.GetTypeMask() & PROC_FLAG_DONE_PERIODIC)) + return false; + + return roll_chance_i(aurEff->GetAmount()); + } + + void HandleProc(ProcEventInfo& eventInfo) { PreventDefaultAction(); - SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); - - if (!(eventInfo.GetTypeMask() & mask) || !spellInfo) - { - return; - } - - // Also check if the proc from the fire traps and black arrow actually comes from the periodic ticks here. - // Normally this wouldn't be required, but we are circumventing the current proc system limitations. - if (((spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE) || (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) - && (spellInfo->Effects[0].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE) - && !(mask & PROC_FLAG_DONE_PERIODIC)) - { - return; - } - - if (!roll_chance_i(aurEff->GetAmount())) - { - return; - } - Unit* caster = eventInfo.GetActor(); caster->CastSpell(caster, SPELL_LOCK_AND_LOAD_TRIGGER, true); - } - - void ApplyMarker(ProcEventInfo& eventInfo) - { - if (IsTargetValid(eventInfo.GetSpellInfo(), eventInfo.GetProcTarget())) - { - Unit* caster = eventInfo.GetActor(); - caster->CastSpell(caster, SPELL_LOCK_AND_LOAD_MARKER, true); - } + caster->CastSpell(caster, SPELL_LOCK_AND_LOAD_MARKER, true); } void Register() override { - DoCheckProc += AuraCheckProcFn(spell_hun_lock_and_load::CheckTrapProc); + DoCheckProc += AuraCheckProcFn(spell_hun_lock_and_load::CheckProc); - OnEffectProc += AuraEffectProcFn(spell_hun_lock_and_load::HandleProcs, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); - OnEffectProc += AuraEffectProcFn(spell_hun_lock_and_load::HandleProcs, EFFECT_1, SPELL_AURA_DUMMY); + DoCheckEffectProc += AuraCheckEffectProcFn(spell_hun_lock_and_load::CheckTrapProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + DoCheckEffectProc += AuraCheckEffectProcFn(spell_hun_lock_and_load::CheckPeriodicProc, EFFECT_1, SPELL_AURA_DUMMY); - AfterProc += AuraProcFn(spell_hun_lock_and_load::ApplyMarker); + OnProc += AuraProcFn(spell_hun_lock_and_load::HandleProc); } }; @@ -1343,6 +1309,159 @@ class spell_hun_target_self_and_pet : public SpellScript } }; +// -34497 - Thrill of the Hunt +class spell_hun_thrill_of_the_hunt : public AuraScript +{ + PrepareAuraScript(spell_hun_thrill_of_the_hunt); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_THRILL_OF_THE_HUNT_MANA }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return; + + Unit* caster = GetTarget(); + if (!caster->IsPlayer()) + return; + + int32 mana = 0; + + Spell* spell = caster->ToPlayer()->m_spellModTakingSpell; + + // Disable charge drop because of Lock and Load + if (spell) + caster->ToPlayer()->SetSpellModTakingSpell(spell, false); + + // Explosive Shot + if (procSpell->SpellFamilyFlags[2] & 0x200) + { + Unit* victim = eventInfo.GetActionTarget(); + if (!victim) + { + if (spell) + caster->ToPlayer()->SetSpellModTakingSpell(spell, true); + return; + } + if (AuraEffect const* pEff = victim->GetAuraEffect(SPELL_AURA_PERIODIC_DUMMY, SPELLFAMILY_HUNTER, 0x0, 0x80000000, 0x0, caster->GetGUID())) + mana = pEff->GetSpellInfo()->CalcPowerCost(caster, SpellSchoolMask(pEff->GetSpellInfo()->SchoolMask)) * 4 / 10 / 3; + } + else + mana = procSpell->CalcPowerCost(caster, SpellSchoolMask(procSpell->SchoolMask)) * 4 / 10; + + if (spell) + caster->ToPlayer()->SetSpellModTakingSpell(spell, true); + + if (mana <= 0) + return; + + caster->CastCustomSpell(SPELL_HUNTER_THRILL_OF_THE_HUNT_MANA, SPELLVALUE_BASE_POINT0, mana, caster, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_hun_thrill_of_the_hunt::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -53290 - Hunting Party +class spell_hun_hunting_party : public AuraScript +{ + PrepareAuraScript(spell_hun_hunting_party); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_REPLENISHMENT }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_HUNTER_REPLENISHMENT, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_hun_hunting_party::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -53228 - Rapid Recuperation +class spell_hun_rapid_recuperation : public AuraScript +{ + PrepareAuraScript(spell_hun_rapid_recuperation); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_RAPID_RECUPERATION_R1, SPELL_HUNTER_RAPID_RECUPERATION_R2 }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return false; + + // This effect only from Rapid Killing (mana regen) + return (procSpell->SpellFamilyFlags[1] & 0x01000000) != 0; + } + + void HandleProc(ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + uint32 triggeredSpell = 0; + switch (GetSpellInfo()->Id) + { + case 53228: // Rank 1 + triggeredSpell = SPELL_HUNTER_RAPID_RECUPERATION_R1; + break; + case 53232: // Rank 2 + triggeredSpell = SPELL_HUNTER_RAPID_RECUPERATION_R2; + break; + default: + return; + } + GetTarget()->CastSpell(GetTarget(), triggeredSpell, true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_hun_rapid_recuperation::CheckProc); + OnProc += AuraProcFn(spell_hun_rapid_recuperation::HandleProc); + } +}; + +// 57870 - Glyph of Mend Pet +class spell_hun_glyph_of_mend_pet : public AuraScript +{ + PrepareAuraScript(spell_hun_glyph_of_mend_pet); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_GLYPH_OF_MEND_PET_HAPPINESS }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + target->CastSpell(target, SPELL_HUNTER_GLYPH_OF_MEND_PET_HAPPINESS, true, nullptr, nullptr, GetTarget()->GetGUID()); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_hun_glyph_of_mend_pet::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + // -53301 - Explosive Shot class spell_hun_explosive_shot : public SpellScript { @@ -1364,6 +1483,153 @@ class spell_hun_explosive_shot : public SpellScript } }; +// 58914 - Kill Command (Pet Aura) +class spell_hun_kill_command_pet : public AuraScript +{ + PrepareAuraScript(spell_hun_kill_command_pet); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_KILL_COMMAND_HUNTER }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + // prevent charge drop (aura has both proc charge and stacks) + PreventDefaultAction(); + + if (Unit* owner = eventInfo.GetActor()->GetOwner()) + owner->RemoveAuraFromStack(SPELL_HUNTER_KILL_COMMAND_HUNTER); + + ModStackAmount(-1); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_hun_kill_command_pet::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -53228 - Rapid Recuperation (trigger) +class spell_hun_rapid_recuperation_trigger : public AuraScript +{ + PrepareAuraScript(spell_hun_rapid_recuperation_trigger); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_HUNTER_RAPID_RECUPERATION_MANA_R1, + SPELL_HUNTER_RAPID_RECUPERATION_MANA_R2 + }); + } + + void HandleRapidFireProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + // Proc only from Rapid Fire + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || !(spellInfo->SpellFamilyFlags[0] & 0x00000020)) + { + PreventDefaultAction(); + return; + } + } + + void HandleRapidKillingProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static uint32 const triggerSpells[2] = { SPELL_HUNTER_RAPID_RECUPERATION_MANA_R1, SPELL_HUNTER_RAPID_RECUPERATION_MANA_R2 }; + + PreventDefaultAction(); + + // Proc only from Rapid Killing + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || !(spellInfo->SpellFamilyFlags[1] & 0x01000000)) + return; + + uint8 rank = GetSpellInfo()->GetRank(); + if (rank > 0 && rank <= 2) + GetTarget()->CastSpell(GetTarget(), triggerSpells[rank - 1], true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_hun_rapid_recuperation_trigger::HandleRapidFireProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + OnEffectProc += AuraEffectProcFn(spell_hun_rapid_recuperation_trigger::HandleRapidKillingProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// -53234 - Piercing Shots +class spell_hun_piercing_shots : public AuraScript +{ + PrepareAuraScript(spell_hun_piercing_shots); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_PIERCING_SHOTS }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetActionTarget() != nullptr; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + DamageInfo* dmgInfo = eventInfo.GetDamageInfo(); + if (!dmgInfo || !dmgInfo->GetDamage()) + return; + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + SpellInfo const* piercingShots = sSpellMgr->AssertSpellInfo(SPELL_HUNTER_PIERCING_SHOTS); + int32 bp = CalculatePct(static_cast(dmgInfo->GetDamage()), aurEff->GetAmount()); + + ASSERT(piercingShots->GetMaxTicks() > 0); + bp /= piercingShots->GetMaxTicks(); + + caster->CastCustomSpell(target, SPELL_HUNTER_PIERCING_SHOTS, &bp, nullptr, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_hun_piercing_shots::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_hun_piercing_shots::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 67151 - Item - Hunter T9 4P Bonus (Steady Shot) +class spell_hun_t9_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_hun_t9_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HUNTER_T9_4P_GREATNESS }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* actor = eventInfo.GetActor(); + return actor && actor->IsPlayer() && actor->ToPlayer()->GetPet(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + caster->CastSpell(caster->ToPlayer()->GetPet(), SPELL_HUNTER_T9_4P_GREATNESS, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_hun_t9_4p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_hun_t9_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_hunter_spell_scripts() { RegisterSpellScript(spell_hun_check_pet_los); @@ -1396,4 +1662,13 @@ void AddSC_hunter_spell_scripts() RegisterSpellScript(spell_hun_bestial_wrath); RegisterSpellScript(spell_hun_target_self_and_pet); RegisterSpellScript(spell_hun_explosive_shot); + RegisterSpellScript(spell_hun_thrill_of_the_hunt); + RegisterSpellScript(spell_hun_hunting_party); + RegisterSpellScript(spell_hun_rapid_recuperation); + RegisterSpellScript(spell_hun_glyph_of_mend_pet); + // Proc system scripts + RegisterSpellScript(spell_hun_kill_command_pet); + RegisterSpellScript(spell_hun_piercing_shots); + RegisterSpellScript(spell_hun_rapid_recuperation_trigger); + RegisterSpellScript(spell_hun_t9_4p_bonus); } diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index d3fcc55b9..e6dd1fcad 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -39,6 +39,240 @@ enum MassiveSeaforiumCharge ITEM_MASSIVE_SEAFORIUM_CHARGE = 39213, }; +enum AlchemistStone +{ + SPELL_ALCHEMISTS_STONE_EXTRA_HEAL = 21399, + SPELL_ALCHEMISTS_STONE_EXTRA_MANA = 21400 +}; + +enum DarkmoonCardGreatness +{ + SPELL_DARKMOON_CARD_STRENGTH = 60229, + SPELL_DARKMOON_CARD_AGILITY = 60233, + SPELL_DARKMOON_CARD_INTELLECT = 60234, + SPELL_DARKMOON_CARD_SPIRIT = 60235 +}; + +enum DeathChoice +{ + SPELL_DEATH_CHOICE_NORMAL_AURA = 67702, + SPELL_DEATH_CHOICE_NORMAL_AGILITY = 67703, + SPELL_DEATH_CHOICE_NORMAL_STRENGTH = 67708, + SPELL_DEATH_CHOICE_HEROIC_AURA = 67771, + SPELL_DEATH_CHOICE_HEROIC_AGILITY = 67772, + SPELL_DEATH_CHOICE_HEROIC_STRENGTH = 67773 +}; + +enum TrinketStackSpells +{ + SPELL_LIGHTNING_CAPACITOR_STACK = 37658, + SPELL_LIGHTNING_CAPACITOR_TRIGGER = 37661, + SPELL_THUNDER_CAPACITOR_STACK = 54842, + SPELL_THUNDER_CAPACITOR_TRIGGER = 54843, + SPELL_TOC25_CASTER_TRINKET_NORMAL_STACK = 67713, + SPELL_TOC25_CASTER_TRINKET_NORMAL_TRIGGER = 67714, + SPELL_TOC25_CASTER_TRINKET_HEROIC_STACK = 67759, + SPELL_TOC25_CASTER_TRINKET_HEROIC_TRIGGER = 67760 +}; + +enum SoulPreserver +{ + SPELL_SOUL_PRESERVER_DRUID = 60512, + SPELL_SOUL_PRESERVER_PALADIN = 60513, + SPELL_SOUL_PRESERVER_PRIEST = 60514, + SPELL_SOUL_PRESERVER_SHAMAN = 60515 +}; + +enum LivingRootOfTheWildheart +{ + SPELL_LIVING_ROOT_BEAR = 37340, + SPELL_LIVING_ROOT_CAT = 37341, + SPELL_LIVING_ROOT_TREE = 37342, + SPELL_LIVING_ROOT_MOONKIN = 37343, + SPELL_LIVING_ROOT_NONE = 37344 +}; + +enum CharmWitchDoctor +{ + SPELL_CHARM_WITCH_DOCTOR_PROC = 43821 +}; + +enum LifegivingGem +{ + SPELL_GIFT_OF_LIFE_1 = 23782, + SPELL_GIFT_OF_LIFE_2 = 23783 +}; + +enum ManaDrain +{ + SPELL_MANA_DRAIN_ENERGIZE = 29471, + SPELL_MANA_DRAIN_LEECH = 27526 +}; + +enum HourglassSand +{ + SPELL_HOURGLASS_SAND_HEAL = 30554, + SPELL_HOURGLASS_SAND_DAMAGE = 30553 +}; + +enum UltrasafeTransporter +{ + SPELL_TRANSPORTER_MALFUNCTION_SMALL = 36178, + SPELL_TRANSPORTER_MALFUNCTION_BIG = 36183, + SPELL_TRANSPORTER_EVIL_TWIN = 23445, + SPELL_TELEPORT_TOSHLEY_STATION = 35974 +}; + +enum PowerCircle +{ + SPELL_LIMITLESS_POWER = 45044 +}; + +enum AuraOfMadness +{ + SPELL_SOCIOPATH = 39511, + SPELL_DELUSIONAL = 40997, + SPELL_KLEPTOMANIA = 40998, + SPELL_MEGALOMANIA = 40999, + SPELL_PARANOIA = 41002, + SPELL_MANIC = 41005, + SPELL_NARCISSISM = 41009, + SPELL_MARTYR_COMPLEX = 41011, + SPELL_DEMENTIA = 41404, + SPELL_DEMENTIA_POS = 41406, + SPELL_DEMENTIA_NEG = 41409, + SAY_MADNESS = 21954 +}; + +enum DeadlyPrecision +{ + SPELL_DEADLY_PRECISION = 71564 +}; + +enum DeathbringersWill +{ + SPELL_STRENGTH_OF_THE_TAUNKA = 71484, + SPELL_AGILITY_OF_THE_VRYKUL = 71485, + SPELL_POWER_OF_THE_TAUNKA = 71486, + SPELL_AIM_OF_THE_IRON_DWARVES = 71491, + SPELL_SPEED_OF_THE_VRYKUL = 71492, + SPELL_AGILITY_OF_THE_VRYKUL_HERO = 71556, + SPELL_POWER_OF_THE_TAUNKA_HERO = 71558, + SPELL_AIM_OF_THE_IRON_DWARVES_HERO = 71559, + SPELL_SPEED_OF_THE_VRYKUL_HERO = 71560, + SPELL_STRENGTH_OF_THE_TAUNKA_HERO = 71561 +}; + +enum DiscerningEyeBeastMisc +{ + SPELL_DISCERNING_EYE_BEAST = 59914 +}; + +enum FrozenShadoweave +{ + SPELL_SHADOWMEND = 39373 +}; + +enum IdolOfLongevity +{ + SPELL_HEALING_TOUCH_MANA = 28848 +}; + +enum Heartpierce +{ + SPELL_INVIGORATION_MANA = 71881, + SPELL_INVIGORATION_ENERGY = 71882, + SPELL_INVIGORATION_RAGE = 71883, + SPELL_INVIGORATION_RP = 71884, + SPELL_INVIGORATION_RP_HERO = 71885, + SPELL_INVIGORATION_RAGE_HERO = 71886, + SPELL_INVIGORATION_ENERGY_HERO = 71887, + SPELL_INVIGORATION_MANA_HERO = 71888 +}; + +enum MarkOfConquest +{ + SPELL_MARK_OF_CONQUEST_ENERGIZE = 33671 +}; + +enum PersistentShieldMisc +{ + SPELL_PERSISTENT_SHIELD_TRIGGERED = 26470 +}; + +enum PetHealing +{ + SPELL_HEALTH_LINK = 37382 +}; + +enum RestlessStrength +{ + SPELL_RESTLESS_STRENGTH = 24662 +}; + +enum UnstablePower +{ + SPELL_UNSTABLE_POWER_AURA = 24659 +}; + +enum CommendationOfKaelthas +{ + SPELL_COMMENDATION_OF_KAELTHAS = 45480 +}; + +enum CorpseTongueCoin +{ + SPELL_CORPSE_TONGUE_COIN = 71633, + SPELL_CORPSE_TONGUE_COIN_HERO = 71634 +}; + +enum CrystalSpireOfKarabor +{ + SPELL_CRYSTAL_SPIRE_OF_KARABOR_MANA = 35476 +}; + +enum SoulHarvestersCharm +{ + SPELL_SOUL_HARVESTERS_CHARM = 60513 +}; + +enum SunwellExaltedNeck +{ + SPELL_LIGHTS_WRATH = 45479, + SPELL_ARCANE_BOLT = 45429, + SPELL_LIGHTS_STRENGTH = 45480, + SPELL_LIGHTS_WARD = 45432 +}; + +enum SwiftHandOfJustice +{ + SPELL_SWIFT_HAND_OF_JUSTICE_HEAL = 59914 +}; + +enum TinyAbominationInAJar +{ + SPELL_MOTE_OF_ANGER = 71432, + SPELL_MANIFEST_ANGER_MAIN_HAND = 71433, + SPELL_MANIFEST_ANGER_OFF_HAND = 71434 +}; + +enum TotemOfFlowingWater +{ + SPELL_TOTEM_OF_FLOWING_WATER_MANA = 28857 +}; + +enum PetrifiedTwilightScale +{ + SPELL_PETRIFIED_TWILIGHT_SCALE_HC = 75480, + SPELL_PETRIFIED_TWILIGHT_SCALE = 75477 +}; + +enum ShardOfTheScale +{ + SPELL_PURIFIED_CAUTERIZING_HEAL = 69733, + SPELL_SHINY_SEARING_FLAMES = 69729 +}; + class spell_item_massive_seaforium_charge : public SpellScript { PrepareSpellScript(spell_item_massive_seaforium_charge); @@ -4293,6 +4527,1671 @@ class spell_item_bloodsail_admiral_hat : public AuraScript } }; +// 17619 - Alchemist's Stone +class spell_item_alchemists_stone : public AuraScript +{ + PrepareAuraScript(spell_item_alchemists_stone); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_ALCHEMISTS_STONE_EXTRA_HEAL, SPELL_ALCHEMISTS_STONE_EXTRA_MANA }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + Unit* caster = eventInfo.GetActionTarget(); + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + uint32 spellId; + int32 bp0; + switch (spellInfo->Effects[i].Effect) + { + case SPELL_EFFECT_HEAL: + spellId = SPELL_ALCHEMISTS_STONE_EXTRA_HEAL; + bp0 = CalculatePct(spellInfo->Effects[i].CalcValue(caster), 40); + break; + case SPELL_EFFECT_ENERGIZE: + spellId = SPELL_ALCHEMISTS_STONE_EXTRA_MANA; + bp0 = CalculatePct(spellInfo->Effects[i].CalcValue(caster), 40); + break; + default: + continue; + } + caster->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, bp0, nullptr, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_alchemists_stone::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 57345 - Darkmoon Card: Greatness +class spell_item_darkmoon_card_greatness : public AuraScript +{ + PrepareAuraScript(spell_item_darkmoon_card_greatness); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DARKMOON_CARD_STRENGTH, + SPELL_DARKMOON_CARD_AGILITY, + SPELL_DARKMOON_CARD_INTELLECT, + SPELL_DARKMOON_CARD_SPIRIT + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + float str = caster->GetStat(STAT_STRENGTH); + float agi = caster->GetStat(STAT_AGILITY); + float intl = caster->GetStat(STAT_INTELLECT); + float spi = caster->GetStat(STAT_SPIRIT); + float stat = 0.0f; + + uint32 spellTrigger = SPELL_DARKMOON_CARD_STRENGTH; + + if (str > stat) + { + spellTrigger = SPELL_DARKMOON_CARD_STRENGTH; + stat = str; + } + + if (agi > stat) + { + spellTrigger = SPELL_DARKMOON_CARD_AGILITY; + stat = agi; + } + + if (intl > stat) + { + spellTrigger = SPELL_DARKMOON_CARD_INTELLECT; + stat = intl; + } + + if (spi > stat) + { + spellTrigger = SPELL_DARKMOON_CARD_SPIRIT; + } + + caster->CastSpell(caster, spellTrigger, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_darkmoon_card_greatness::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 67702, 67771 - Death's Choice/Death's Verdict +class spell_item_death_choice : public AuraScript +{ + PrepareAuraScript(spell_item_death_choice); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_DEATH_CHOICE_NORMAL_STRENGTH, + SPELL_DEATH_CHOICE_NORMAL_AGILITY, + SPELL_DEATH_CHOICE_HEROIC_STRENGTH, + SPELL_DEATH_CHOICE_HEROIC_AGILITY + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + float str = caster->GetStat(STAT_STRENGTH); + float agi = caster->GetStat(STAT_AGILITY); + + switch (aurEff->GetId()) + { + case SPELL_DEATH_CHOICE_NORMAL_AURA: + if (str > agi) + caster->CastSpell(caster, SPELL_DEATH_CHOICE_NORMAL_STRENGTH, true, nullptr, aurEff); + else + caster->CastSpell(caster, SPELL_DEATH_CHOICE_NORMAL_AGILITY, true, nullptr, aurEff); + break; + case SPELL_DEATH_CHOICE_HEROIC_AURA: + if (str > agi) + caster->CastSpell(caster, SPELL_DEATH_CHOICE_HEROIC_STRENGTH, true, nullptr, aurEff); + else + caster->CastSpell(caster, SPELL_DEATH_CHOICE_HEROIC_AGILITY, true, nullptr, aurEff); + break; + default: + break; + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_death_choice::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 37657 - The Lightning Capacitor +// 54841 - Thunder Capacitor +// 67712 - Item - Coliseum 25 Normal Caster Trinket +// 67758 - Item - Coliseum 25 Heroic Caster Trinket +template +class spell_item_trinket_stack : public AuraScript +{ + PrepareAuraScript(spell_item_trinket_stack); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ StackSpell, TriggerSpell }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + + caster->CastSpell(caster, StackSpell, aurEff); + + Aura* dummy = caster->GetAura(StackSpell); + + if (!dummy || dummy->GetStackAmount() < aurEff->GetAmount()) + return; + + caster->RemoveAurasDueToSpell(StackSpell); + if (Unit* target = eventInfo.GetActionTarget()) + caster->CastSpell(target, TriggerSpell, aurEff); + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(StackSpell); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_trinket_stack::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + AfterEffectRemove += AuraEffectRemoveFn(spell_item_trinket_stack::OnRemove, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + } +}; + +using spell_item_lightning_capacitor = spell_item_trinket_stack; +using spell_item_thunder_capacitor = spell_item_trinket_stack; +using spell_item_toc25_caster_trinket_normal = spell_item_trinket_stack; +using spell_item_toc25_caster_trinket_heroic = spell_item_trinket_stack; + +// 60510 - Soul Preserver +class spell_item_soul_preserver : public AuraScript +{ + PrepareAuraScript(spell_item_soul_preserver); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_SOUL_PRESERVER_DRUID, + SPELL_SOUL_PRESERVER_PALADIN, + SPELL_SOUL_PRESERVER_PRIEST, + SPELL_SOUL_PRESERVER_SHAMAN + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + + switch (caster->getClass()) + { + case CLASS_DRUID: + caster->CastSpell(caster, SPELL_SOUL_PRESERVER_DRUID, true, nullptr, aurEff); + break; + case CLASS_PALADIN: + caster->CastSpell(caster, SPELL_SOUL_PRESERVER_PALADIN, true, nullptr, aurEff); + break; + case CLASS_PRIEST: + caster->CastSpell(caster, SPELL_SOUL_PRESERVER_PRIEST, true, nullptr, aurEff); + break; + case CLASS_SHAMAN: + caster->CastSpell(caster, SPELL_SOUL_PRESERVER_SHAMAN, true, nullptr, aurEff); + break; + default: + break; + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_soul_preserver::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 43820 - Amani Charm of the Witch Doctor +class spell_item_charm_witch_doctor : public AuraScript +{ + PrepareAuraScript(spell_item_charm_witch_doctor); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_CHARM_WITCH_DOCTOR_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + if (Unit* target = eventInfo.GetActionTarget()) + { + int32 bp = CalculatePct(target->GetCreateHealth(), GetSpellInfo()->GetEffect(EFFECT_1).CalcValue()); + eventInfo.GetActor()->CastCustomSpell(SPELL_CHARM_WITCH_DOCTOR_PROC, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_charm_witch_doctor::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 23725 - Gift of Life (Lifegiving Gem) +class spell_item_lifegiving_gem : public SpellScript +{ + PrepareSpellScript(spell_item_lifegiving_gem); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_GIFT_OF_LIFE_1, SPELL_GIFT_OF_LIFE_2 }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + caster->CastSpell(caster, SPELL_GIFT_OF_LIFE_1, true); + caster->CastSpell(caster, SPELL_GIFT_OF_LIFE_2, true); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_item_lifegiving_gem::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + +// 27522, 40336 - Mana Drain +class spell_item_mana_drain : public AuraScript +{ + PrepareAuraScript(spell_item_mana_drain); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MANA_DRAIN_ENERGIZE, SPELL_MANA_DRAIN_LEECH }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + if (caster->IsAlive()) + caster->CastSpell(caster, SPELL_MANA_DRAIN_ENERGIZE, true, nullptr, aurEff); + + if (target && target->IsAlive()) + caster->CastSpell(target, SPELL_MANA_DRAIN_LEECH, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_mana_drain::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 13180 - Gnomish Mind Control Cap +class spell_item_mind_control_cap : public SpellScript +{ + PrepareSpellScript(spell_item_mind_control_cap); + + bool Load() override + { + if (!GetCastItem()) + return false; + return GetCaster()->IsPlayer(); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + if (Unit* target = GetHitUnit()) + { + if (roll_chance_i(95)) + caster->CastSpell(target, roll_chance_i(50) ? 13181 : 13181, GetCastItem()); + else + target->CastSpell(caster, 13181, true); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_mind_control_cap::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + +// 30536 - Hourglass Sand +class spell_item_hourglass_sand : public SpellScript +{ + PrepareSpellScript(spell_item_hourglass_sand); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HOURGLASS_SAND_HEAL, SPELL_HOURGLASS_SAND_DAMAGE }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitUnit(), GetCaster()->IsFriendlyTo(GetHitUnit()) ? SPELL_HOURGLASS_SAND_HEAL : SPELL_HOURGLASS_SAND_DAMAGE, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_hourglass_sand::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + +// 36941 - Ultrasafe Transporter: Toshley's Station +class spell_item_ultrasafe_transporter : public SpellScript +{ + PrepareSpellScript(spell_item_ultrasafe_transporter); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_TRANSPORTER_MALFUNCTION_SMALL, SPELL_TRANSPORTER_MALFUNCTION_BIG, SPELL_TRANSPORTER_EVIL_TWIN, SPELL_TELEPORT_TOSHLEY_STATION }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + caster->CastSpell(caster, SPELL_TELEPORT_TOSHLEY_STATION, true); + if (roll_chance_i(5)) + caster->CastSpell(caster, RAND(SPELL_TRANSPORTER_MALFUNCTION_SMALL, SPELL_TRANSPORTER_MALFUNCTION_BIG, SPELL_TRANSPORTER_EVIL_TWIN), true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_ultrasafe_transporter::HandleDummy, EFFECT_0, SPELL_EFFECT_TELEPORT_UNITS); + } +}; + +// 45043 - Power Circle (Mage T5 Set Bonus) +class spell_item_power_circle : public AuraScript +{ + PrepareAuraScript(spell_item_power_circle); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_LIMITLESS_POWER }); + } + + void OnAuraInit() + { + Unit* caster = GetCaster(); + if (!caster) + return; + + caster->CastSpell(caster, SPELL_LIMITLESS_POWER, true); + } + + void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_LIMITLESS_POWER); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_item_power_circle::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +enum ThrallmarAndHonorHoldFavor +{ + SPELL_BUFFBOT_BUFF_EFFECT = 32172 +}; + +// 32096 - Thrallmar's Favor +// 32098 - Honor Hold's Favor +class spell_item_thrallmar_and_honor_hold_favor : public AuraScript +{ + PrepareAuraScript(spell_item_thrallmar_and_honor_hold_favor); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_BUFFBOT_BUFF_EFFECT }); + } + + void AfterApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->CastSpell(GetTarget(), SPELL_BUFFBOT_BUFF_EFFECT, true); + } + + void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_BUFFBOT_BUFF_EFFECT); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_item_thrallmar_and_honor_hold_favor::AfterApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + AfterEffectRemove += AuraEffectRemoveFn(spell_item_thrallmar_and_honor_hold_favor::AfterRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +enum DrumsOfForgottenKings +{ + SPELL_BLESSING_OF_FORGOTTEN_KINGS = 72586 +}; + +// 69378 - Blessing of Forgotten Kings +class spell_item_drums_of_forgotten_kings : public SpellScript +{ + PrepareSpellScript(spell_item_drums_of_forgotten_kings); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_BLESSING_OF_FORGOTTEN_KINGS }); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitUnit(), SPELL_BLESSING_OF_FORGOTTEN_KINGS, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_drums_of_forgotten_kings::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +enum DrumsOfTheWild +{ + SPELL_GIFT_OF_THE_WILD = 72588 +}; + +// 69381 - Gift of the Wild +class spell_item_drums_of_the_wild : public SpellScript +{ + PrepareSpellScript(spell_item_drums_of_the_wild); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_GIFT_OF_THE_WILD }); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitUnit(), SPELL_GIFT_OF_THE_WILD, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_drums_of_the_wild::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +enum DarkmoonCardIllusion +{ + SPELL_DARKMOON_CARD_ILLUSION = 60242 +}; + +// 57350 - Illusionary Barrier +class spell_item_darkmoon_card_illusion : public AuraScript +{ + PrepareAuraScript(spell_item_darkmoon_card_illusion); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DARKMOON_CARD_ILLUSION }); + } + + void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->CastSpell(GetTarget(), SPELL_DARKMOON_CARD_ILLUSION, true); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_item_darkmoon_card_illusion::AfterRemove, EFFECT_0, SPELL_AURA_SCHOOL_ABSORB, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 28374 - Mad Alchemist's Potion +class spell_item_mad_alchemists_potion : public SpellScript +{ + PrepareSpellScript(spell_item_mad_alchemists_potion); + + void SecondaryEffect() + { + std::vector availableElixirs = + { + // Battle Elixirs + 33720, // Onslaught Elixir + 54452, // Adept's Elixir + 33726, // Elixir of Mastery + 28490, // Elixir of Major Strength + 28491, // Elixir of Healing Power + 28493, // Elixir of Major Frost Power + 54494, // Elixir of Major Agility + 28501, // Elixir of Major Firepower + 28503, // Elixir of Major Shadow Power + 38954, // Fel Strength Elixir + // Guardian Elixirs + 39625, // Elixir of Major Fortitude + 39626, // Earthen Elixir + 39627, // Elixir of Draenic Wisdom + 39628, // Elixir of Ironskin + 28502, // Elixir of Major Defense + 28514, // Elixir of Empowerment + // Other + 28489, // Elixir of Camouflage + 28496 // Elixir of the Searching Eye + }; + + Unit* target = GetCaster(); + + if (target->getPowerType() == POWER_MANA) + availableElixirs.push_back(28509); // Elixir of Major Mageblood + + uint32 chosenElixir = Acore::Containers::SelectRandomContainerElement(availableElixirs); + + bool useElixir = true; + + SpellGroup chosenSpellGroup = SPELL_GROUP_NONE; + if (sSpellMgr->IsSpellMemberOfSpellGroup(chosenElixir, SPELL_GROUP_ELIXIR_BATTLE)) + chosenSpellGroup = SPELL_GROUP_ELIXIR_BATTLE; + if (sSpellMgr->IsSpellMemberOfSpellGroup(chosenElixir, SPELL_GROUP_ELIXIR_GUARDIAN)) + chosenSpellGroup = SPELL_GROUP_ELIXIR_GUARDIAN; + + if (chosenSpellGroup != SPELL_GROUP_NONE) + { + Unit::AuraApplicationMap const& auraMap = target->GetAppliedAuras(); + for (auto itr = auraMap.begin(); itr != auraMap.end(); ++itr) + { + uint32 spellId = itr->second->GetBase()->GetId(); + if (sSpellMgr->IsSpellMemberOfSpellGroup(spellId, chosenSpellGroup) && spellId != chosenElixir) + { + useElixir = false; + break; + } + } + } + + if (useElixir) + target->CastSpell(target, chosenElixir, GetCastItem()); + } + + void Register() override + { + AfterCast += SpellCastFn(spell_item_mad_alchemists_potion::SecondaryEffect); + } +}; + +// 47770 - Roll 'dem Bones +class spell_item_decahedral_dwarven_dice : public SpellScript +{ + PrepareSpellScript(spell_item_decahedral_dwarven_dice); + + enum + { + TEXT_DECAHEDRAL_DWARVEN_DICE = 26147 + }; + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sObjectMgr->GetBroadcastText(TEXT_DECAHEDRAL_DWARVEN_DICE)) + return false; + return true; + } + + bool Load() override + { + return GetCaster()->IsPlayer(); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + GetCaster()->TextEmote(TEXT_DECAHEDRAL_DWARVEN_DICE, GetHitUnit()); + + static uint32 const minimum = 1; + static uint32 const maximum = 100; + + GetCaster()->ToPlayer()->DoRandomRoll(minimum, maximum); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_item_decahedral_dwarven_dice::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +// 39446 - Aura of Madness +class spell_item_aura_of_madness : public AuraScript +{ + PrepareAuraScript(spell_item_aura_of_madness); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_MEGALOMANIA, + SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA + }) && sObjectMgr->GetBroadcastText(SAY_MADNESS); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static std::vector const triggeredSpells[MAX_CLASSES] = + { + //CLASS_NONE + { }, + //CLASS_WARRIOR + { SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_MARTYR_COMPLEX }, + //CLASS_PALADIN + { SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_HUNTER + { SPELL_DELUSIONAL, SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_ROGUE + { SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_MARTYR_COMPLEX }, + //CLASS_PRIEST + { SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_DEATH_KNIGHT + { SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_MARTYR_COMPLEX }, + //CLASS_SHAMAN + { SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_MAGE + { SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_WARLOCK + { SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA }, + //CLASS_UNK + { }, + //CLASS_DRUID + { SPELL_SOCIOPATH, SPELL_DELUSIONAL, SPELL_KLEPTOMANIA, SPELL_MEGALOMANIA, SPELL_PARANOIA, SPELL_MANIC, SPELL_NARCISSISM, SPELL_MARTYR_COMPLEX, SPELL_DEMENTIA } + }; + + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + std::vector const& randomSpells = triggeredSpells[caster->getClass()]; + if (randomSpells.empty()) + return; + + uint32 spellId = Acore::Containers::SelectRandomContainerElement(randomSpells); + caster->CastSpell(caster, spellId, true, nullptr, aurEff); + + if (roll_chance_i(10)) + caster->Unit::Say(SAY_MADNESS); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_aura_of_madness::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 41404 - Dementia +class spell_item_dementia : public AuraScript +{ + PrepareAuraScript(spell_item_dementia); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DEMENTIA_POS, SPELL_DEMENTIA_NEG }); + } + + void HandlePeriodicDummy(AuraEffect const* aurEff) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), RAND(SPELL_DEMENTIA_POS, SPELL_DEMENTIA_NEG), true, nullptr, aurEff); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_item_dementia::HandlePeriodicDummy, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// 71564 - Deadly Precision +// This spell uses STACKS (not charges) - must manually remove stacks on proc +class spell_item_deadly_precision : public AuraScript +{ + PrepareAuraScript(spell_item_deadly_precision); + + void HandleStackDrop(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->RemoveAuraFromStack(GetId(), GetTarget()->GetGUID()); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_deadly_precision::HandleStackDrop, EFFECT_0, SPELL_AURA_MOD_RATING); + } +}; + +// 71563 - Deadly Precision Dummy +class spell_item_deadly_precision_dummy : public SpellScript +{ + PrepareSpellScript(spell_item_deadly_precision_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DEADLY_PRECISION }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_DEADLY_PRECISION); + GetCaster()->CastCustomSpell(SPELL_DEADLY_PRECISION, SPELLVALUE_AURA_STACK, spellInfo->StackAmount, GetCaster(), true); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_item_deadly_precision_dummy::HandleDummy, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } +}; + +// 71519 - Deathbringer's Will (Normal) +class spell_item_deathbringers_will_normal : public AuraScript +{ + PrepareAuraScript(spell_item_deathbringers_will_normal); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_STRENGTH_OF_THE_TAUNKA, SPELL_AGILITY_OF_THE_VRYKUL, + SPELL_POWER_OF_THE_TAUNKA, SPELL_AIM_OF_THE_IRON_DWARVES, SPELL_SPEED_OF_THE_VRYKUL + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static std::vector const triggeredSpells[MAX_CLASSES] = + { + { }, // CLASS_NONE + { SPELL_STRENGTH_OF_THE_TAUNKA, SPELL_AIM_OF_THE_IRON_DWARVES, SPELL_SPEED_OF_THE_VRYKUL }, // CLASS_WARRIOR + { SPELL_STRENGTH_OF_THE_TAUNKA, SPELL_AIM_OF_THE_IRON_DWARVES, SPELL_SPEED_OF_THE_VRYKUL }, // CLASS_PALADIN + { SPELL_AGILITY_OF_THE_VRYKUL, SPELL_AIM_OF_THE_IRON_DWARVES, SPELL_POWER_OF_THE_TAUNKA }, // CLASS_HUNTER + { SPELL_AGILITY_OF_THE_VRYKUL, SPELL_SPEED_OF_THE_VRYKUL, SPELL_POWER_OF_THE_TAUNKA }, // CLASS_ROGUE + { }, // CLASS_PRIEST + { SPELL_STRENGTH_OF_THE_TAUNKA, SPELL_AIM_OF_THE_IRON_DWARVES, SPELL_SPEED_OF_THE_VRYKUL }, // CLASS_DEATH_KNIGHT + { SPELL_AGILITY_OF_THE_VRYKUL, SPELL_SPEED_OF_THE_VRYKUL, SPELL_POWER_OF_THE_TAUNKA }, // CLASS_SHAMAN + { }, // CLASS_MAGE + { }, // CLASS_WARLOCK + { }, // CLASS_UNK + { SPELL_STRENGTH_OF_THE_TAUNKA, SPELL_AGILITY_OF_THE_VRYKUL, SPELL_SPEED_OF_THE_VRYKUL } // CLASS_DRUID + }; + + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + std::vector const& randomSpells = triggeredSpells[caster->getClass()]; + if (randomSpells.empty()) + return; + + uint32 spellId = Acore::Containers::SelectRandomContainerElement(randomSpells); + caster->CastSpell(caster, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_deathbringers_will_normal::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 71562 - Deathbringer's Will (Heroic) +class spell_item_deathbringers_will_heroic : public AuraScript +{ + PrepareAuraScript(spell_item_deathbringers_will_heroic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_STRENGTH_OF_THE_TAUNKA_HERO, SPELL_AGILITY_OF_THE_VRYKUL_HERO, + SPELL_POWER_OF_THE_TAUNKA_HERO, SPELL_AIM_OF_THE_IRON_DWARVES_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static std::vector const triggeredSpells[MAX_CLASSES] = + { + { }, // CLASS_NONE + { SPELL_STRENGTH_OF_THE_TAUNKA_HERO, SPELL_AIM_OF_THE_IRON_DWARVES_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO }, // CLASS_WARRIOR + { SPELL_STRENGTH_OF_THE_TAUNKA_HERO, SPELL_AIM_OF_THE_IRON_DWARVES_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO }, // CLASS_PALADIN + { SPELL_AGILITY_OF_THE_VRYKUL_HERO, SPELL_AIM_OF_THE_IRON_DWARVES_HERO, SPELL_POWER_OF_THE_TAUNKA_HERO }, // CLASS_HUNTER + { SPELL_AGILITY_OF_THE_VRYKUL_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO, SPELL_POWER_OF_THE_TAUNKA_HERO }, // CLASS_ROGUE + { }, // CLASS_PRIEST + { SPELL_STRENGTH_OF_THE_TAUNKA_HERO, SPELL_AIM_OF_THE_IRON_DWARVES_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO }, // CLASS_DEATH_KNIGHT + { SPELL_AGILITY_OF_THE_VRYKUL_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO, SPELL_POWER_OF_THE_TAUNKA_HERO }, // CLASS_SHAMAN + { }, // CLASS_MAGE + { }, // CLASS_WARLOCK + { }, // CLASS_UNK + { SPELL_STRENGTH_OF_THE_TAUNKA_HERO, SPELL_AGILITY_OF_THE_VRYKUL_HERO, SPELL_SPEED_OF_THE_VRYKUL_HERO } // CLASS_DRUID + }; + + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + std::vector const& randomSpells = triggeredSpells[caster->getClass()]; + if (randomSpells.empty()) + return; + + uint32 spellId = Acore::Containers::SelectRandomContainerElement(randomSpells); + caster->CastSpell(caster, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_deathbringers_will_heroic::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 59915 - Discerning Eye of the Beast Dummy +class spell_item_discerning_eye_beast_dummy : public AuraScript +{ + PrepareAuraScript(spell_item_discerning_eye_beast_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DISCERNING_EYE_BEAST }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_DISCERNING_EYE_BEAST, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_discerning_eye_beast_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 39372 - Frozen Shadoweave +class spell_item_frozen_shadoweave : public AuraScript +{ + PrepareAuraScript(spell_item_frozen_shadoweave); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHADOWMEND }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* caster = eventInfo.GetActor(); + int32 bp0 = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()); + caster->CastCustomSpell(SPELL_SHADOWMEND, SPELLVALUE_BASE_POINT0, bp0, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_frozen_shadoweave::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 28847 - Healing Touch Refund +class spell_item_healing_touch_refund : public AuraScript +{ + PrepareAuraScript(spell_item_healing_touch_refund); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HEALING_TOUCH_MANA }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_HEALING_TOUCH_MANA, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_healing_touch_refund::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 71880 - Heartpierce (Normal) +class spell_item_heartpierce : public AuraScript +{ + PrepareAuraScript(spell_item_heartpierce); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_INVIGORATION_MANA, SPELL_INVIGORATION_ENERGY, + SPELL_INVIGORATION_RAGE, SPELL_INVIGORATION_RP + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + + uint32 spellId; + switch (caster->getPowerType()) + { + case POWER_MANA: + spellId = SPELL_INVIGORATION_MANA; + break; + case POWER_ENERGY: + spellId = SPELL_INVIGORATION_ENERGY; + break; + case POWER_RAGE: + spellId = SPELL_INVIGORATION_RAGE; + break; + case POWER_RUNIC_POWER: + spellId = SPELL_INVIGORATION_RP; + break; + default: + return; + } + + caster->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_heartpierce::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 71892 - Heartpierce (Heroic) +class spell_item_heartpierce_hero : public AuraScript +{ + PrepareAuraScript(spell_item_heartpierce_hero); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_INVIGORATION_MANA_HERO, SPELL_INVIGORATION_ENERGY_HERO, + SPELL_INVIGORATION_RAGE_HERO, SPELL_INVIGORATION_RP_HERO + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + + uint32 spellId; + switch (caster->getPowerType()) + { + case POWER_MANA: + spellId = SPELL_INVIGORATION_MANA_HERO; + break; + case POWER_ENERGY: + spellId = SPELL_INVIGORATION_ENERGY_HERO; + break; + case POWER_RAGE: + spellId = SPELL_INVIGORATION_RAGE_HERO; + break; + case POWER_RUNIC_POWER: + spellId = SPELL_INVIGORATION_RP_HERO; + break; + default: + return; + } + + caster->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_heartpierce_hero::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 33670 - Mark of Conquest +class spell_item_mark_of_conquest : public AuraScript +{ + PrepareAuraScript(spell_item_mark_of_conquest); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MARK_OF_CONQUEST_ENERGIZE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + if (eventInfo.GetTypeMask() & (PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS)) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_MARK_OF_CONQUEST_ENERGIZE, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_mark_of_conquest::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 35095 - Pendant of the Violet Eye +class spell_item_pendant_of_the_violet_eye : public AuraScript +{ + PrepareAuraScript(spell_item_pendant_of_the_violet_eye); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) + return spellInfo->PowerType == POWER_MANA || (spellInfo->ManaCost != 0 || spellInfo->ManaCostPercentage != 0 || spellInfo->ManaCostPerlevel != 0); + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_pendant_of_the_violet_eye::CheckProc); + } +}; + +// 26467 - Persistent Shield +class spell_item_persistent_shield : public AuraScript +{ + PrepareAuraScript(spell_item_persistent_shield); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PERSISTENT_SHIELD_TRIGGERED }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetHealInfo() && eventInfo.GetHealInfo()->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + int32 bp0 = CalculatePct(static_cast(eventInfo.GetHealInfo()->GetHeal()), 15); + + // Scarab Brooch does not replace stronger shields + if (AuraEffect const* shield = target->GetAuraEffect(SPELL_PERSISTENT_SHIELD_TRIGGERED, EFFECT_0, caster->GetGUID())) + if (shield->GetAmount() > bp0) + return; + + caster->CastCustomSpell(SPELL_PERSISTENT_SHIELD_TRIGGERED, SPELLVALUE_BASE_POINT0, bp0, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_persistent_shield::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_item_persistent_shield::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 37381 - Pet Healing +class spell_item_pet_healing : public AuraScript +{ + PrepareAuraScript(spell_item_pet_healing); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HEALTH_LINK }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + Unit* caster = eventInfo.GetActor(); + int32 bp0 = CalculatePct(static_cast(healInfo->GetHeal()), aurEff->GetAmount()); + caster->CastCustomSpell(SPELL_HEALTH_LINK, SPELLVALUE_BASE_POINT0, bp0, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_pet_healing::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 24661 - Restless Strength +class spell_item_restless_strength : public AuraScript +{ + PrepareAuraScript(spell_item_restless_strength); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_RESTLESS_STRENGTH }); + } + + void HandleProc(ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->RemoveAuraFromStack(SPELL_RESTLESS_STRENGTH); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_restless_strength::HandleProc); + } +}; + +// 24658 - Unstable Power +class spell_item_unstable_power : public AuraScript +{ + PrepareAuraScript(spell_item_unstable_power); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_UNSTABLE_POWER_AURA }); + } + + void HandleProc(ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->RemoveAuraFromStack(SPELL_UNSTABLE_POWER_AURA); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_unstable_power::HandleProc); + } +}; + +// 45478 - Commendation of Kael'thas +class spell_item_commendation_of_kaelthas : public AuraScript +{ + PrepareAuraScript(spell_item_commendation_of_kaelthas); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (HealInfo* healInfo = eventInfo.GetHealInfo()) + if (Unit* target = healInfo->GetTarget()) + if (target->GetHealth() + healInfo->GetEffectiveHeal() >= target->GetMaxHealth()) + return true; + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_commendation_of_kaelthas::CheckProc); + } +}; + +// 71632 - Corpse Tongue Coin +class spell_item_corpse_tongue_coin : public AuraScript +{ + PrepareAuraScript(spell_item_corpse_tongue_coin); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_CORPSE_TONGUE_COIN }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell((Unit*)nullptr, SPELL_CORPSE_TONGUE_COIN, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_corpse_tongue_coin::HandleProc); + } +}; + +// 71639 - Corpse Tongue Coin (Heroic) +class spell_item_corpse_tongue_coin_heroic : public AuraScript +{ + PrepareAuraScript(spell_item_corpse_tongue_coin_heroic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_CORPSE_TONGUE_COIN_HERO }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell((Unit*)nullptr, SPELL_CORPSE_TONGUE_COIN_HERO, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_corpse_tongue_coin_heroic::HandleProc); + } +}; + +// 34774 - Crystal Spire of Karabor +class spell_item_crystal_spire_of_karabor : public AuraScript +{ + PrepareAuraScript(spell_item_crystal_spire_of_karabor); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) + return spellInfo->PowerType == POWER_MANA; + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_crystal_spire_of_karabor::CheckProc); + } +}; + +// 60512 - Soul Harvester's Charm +class spell_item_soul_harvesters_charm : public AuraScript +{ + PrepareAuraScript(spell_item_soul_harvesters_charm); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SOUL_HARVESTERS_CHARM }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell((Unit*)nullptr, SPELL_SOUL_HARVESTERS_CHARM, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_soul_harvesters_charm::HandleProc); + } +}; + +// 45481 - Sunwell Exalted Caster Neck +class spell_item_sunwell_exalted_caster_neck : public AuraScript +{ + PrepareAuraScript(spell_item_sunwell_exalted_caster_neck); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_LIGHTS_WRATH, SPELL_ARCANE_BOLT }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + uint32 spellId = caster->getClass() == CLASS_PALADIN ? SPELL_LIGHTS_WRATH : SPELL_ARCANE_BOLT; + caster->CastSpell(eventInfo.GetProcTarget(), spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_sunwell_exalted_caster_neck::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 45482 - Sunwell Exalted Healer Neck +class spell_item_sunwell_exalted_healer_neck : public AuraScript +{ + PrepareAuraScript(spell_item_sunwell_exalted_healer_neck); + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetHealInfo() && eventInfo.GetHealInfo()->GetHeal(); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_sunwell_exalted_healer_neck::CheckProc); + } +}; + +// 45483 - Sunwell Exalted Melee Neck +class spell_item_sunwell_exalted_melee_neck : public AuraScript +{ + PrepareAuraScript(spell_item_sunwell_exalted_melee_neck); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_LIGHTS_STRENGTH }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_LIGHTS_STRENGTH, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_sunwell_exalted_melee_neck::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 45484 - Sunwell Exalted Tank Neck +class spell_item_sunwell_exalted_tank_neck : public AuraScript +{ + PrepareAuraScript(spell_item_sunwell_exalted_tank_neck); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_LIGHTS_WARD }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_LIGHTS_WARD, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_sunwell_exalted_tank_neck::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 59906 - Swift Hand of Justice Dummy +class spell_item_swift_hand_justice_dummy : public AuraScript +{ + PrepareAuraScript(spell_item_swift_hand_justice_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SWIFT_HAND_OF_JUSTICE_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_SWIFT_HAND_OF_JUSTICE_HEAL, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_swift_hand_justice_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 71406 - Tiny Abomination in a Jar +class spell_item_tiny_abomination_in_a_jar : public AuraScript +{ + PrepareAuraScript(spell_item_tiny_abomination_in_a_jar); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MOTE_OF_ANGER, SPELL_MANIFEST_ANGER_MAIN_HAND, SPELL_MANIFEST_ANGER_OFF_HAND }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + caster->CastSpell(nullptr, SPELL_MOTE_OF_ANGER, true, nullptr, aurEff); + Aura const* motes = caster->GetAura(SPELL_MOTE_OF_ANGER); + if (!motes || motes->GetStackAmount() < 8) + return; + + caster->RemoveAurasDueToSpell(SPELL_MOTE_OF_ANGER); + uint32 spellId = SPELL_MANIFEST_ANGER_MAIN_HAND; + if (Player* player = caster->ToPlayer()) + if (player->GetWeaponForAttack(OFF_ATTACK, true) && roll_chance_i(50)) + spellId = SPELL_MANIFEST_ANGER_OFF_HAND; + + caster->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_MOTE_OF_ANGER); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_tiny_abomination_in_a_jar::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + AfterEffectRemove += AuraEffectRemoveFn(spell_item_tiny_abomination_in_a_jar::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 71545 - Tiny Abomination in a Jar (Heroic) +class spell_item_tiny_abomination_in_a_jar_hero : public AuraScript +{ + PrepareAuraScript(spell_item_tiny_abomination_in_a_jar_hero); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MOTE_OF_ANGER, SPELL_MANIFEST_ANGER_MAIN_HAND, SPELL_MANIFEST_ANGER_OFF_HAND }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + caster->CastSpell(nullptr, SPELL_MOTE_OF_ANGER, true, nullptr, aurEff); + Aura const* motes = caster->GetAura(SPELL_MOTE_OF_ANGER); + if (!motes || motes->GetStackAmount() < 7) + return; + + caster->RemoveAurasDueToSpell(SPELL_MOTE_OF_ANGER); + uint32 spellId = SPELL_MANIFEST_ANGER_MAIN_HAND; + if (Player* player = caster->ToPlayer()) + if (player->GetWeaponForAttack(OFF_ATTACK, true) && roll_chance_i(50)) + spellId = SPELL_MANIFEST_ANGER_OFF_HAND; + + caster->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_MOTE_OF_ANGER); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_tiny_abomination_in_a_jar_hero::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + AfterEffectRemove += AuraEffectRemoveFn(spell_item_tiny_abomination_in_a_jar_hero::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 28856 - Totem of Flowing Water +class spell_item_totem_of_flowing_water : public AuraScript +{ + PrepareAuraScript(spell_item_totem_of_flowing_water); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_TOTEM_OF_FLOWING_WATER_MANA }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(nullptr, SPELL_TOTEM_OF_FLOWING_WATER_MANA, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_totem_of_flowing_water::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 75466 - Petrified Twilight Scale +class spell_item_petrified_twilight_scale : public AuraScript +{ + PrepareAuraScript(spell_item_petrified_twilight_scale); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PETRIFIED_TWILIGHT_SCALE }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActionTarget()->CastSpell((Unit*)nullptr, SPELL_PETRIFIED_TWILIGHT_SCALE, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_petrified_twilight_scale::HandleProc); + } +}; + +// 75473 - Petrified Twilight Scale (Heroic) +class spell_item_petrified_twilight_scale_heroic : public AuraScript +{ + PrepareAuraScript(spell_item_petrified_twilight_scale_heroic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PETRIFIED_TWILIGHT_SCALE_HC }); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActionTarget()->CastSpell((Unit*)nullptr, SPELL_PETRIFIED_TWILIGHT_SCALE_HC, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_item_petrified_twilight_scale_heroic::HandleProc); + } +}; + +// 69739 - Purified Shard of the Scale +class spell_item_purified_shard_of_the_scale : public AuraScript +{ + PrepareAuraScript(spell_item_purified_shard_of_the_scale); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PURIFIED_CAUTERIZING_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetProcTarget(), SPELL_PURIFIED_CAUTERIZING_HEAL, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_purified_shard_of_the_scale::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 69755 - Shiny Shard of the Scale +class spell_item_shiny_shard_of_the_scale : public AuraScript +{ + PrepareAuraScript(spell_item_shiny_shard_of_the_scale); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHINY_SEARING_FLAMES }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetProcTarget(), SPELL_SHINY_SEARING_FLAMES, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_item_shiny_shard_of_the_scale::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 37336 - Living Root of the Wildheart +class spell_item_living_root_of_the_wildheart : public AuraScript +{ + PrepareAuraScript(spell_item_living_root_of_the_wildheart); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_LIVING_ROOT_BEAR, + SPELL_LIVING_ROOT_CAT, + SPELL_LIVING_ROOT_MOONKIN, + SPELL_LIVING_ROOT_NONE, + SPELL_LIVING_ROOT_TREE + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* target = eventInfo.GetActor(); + + switch (target->GetShapeshiftForm()) + { + case FORM_BEAR: + case FORM_DIREBEAR: + case FORM_CAT: + case FORM_MOONKIN: + case FORM_NONE: + case FORM_TREE: + return true; + default: + break; + } + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActor(); + uint32 triggerspell = 0; + + switch (target->GetShapeshiftForm()) + { + case FORM_BEAR: + case FORM_DIREBEAR: + triggerspell = SPELL_LIVING_ROOT_BEAR; + break; + case FORM_CAT: + triggerspell = SPELL_LIVING_ROOT_CAT; + break; + case FORM_MOONKIN: + triggerspell = SPELL_LIVING_ROOT_MOONKIN; + break; + case FORM_NONE: + triggerspell = SPELL_LIVING_ROOT_NONE; + break; + case FORM_TREE: + triggerspell = SPELL_LIVING_ROOT_TREE; + break; + default: + return; + } + + target->CastSpell(target, triggerspell, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_item_living_root_of_the_wildheart::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_item_living_root_of_the_wildheart::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_item_spell_scripts() { RegisterSpellScript(spell_item_massive_seaforium_charge); @@ -4423,4 +6322,60 @@ void AddSC_item_spell_scripts() RegisterSpellScript(spell_item_multiphase_goggles); RegisterSpellScript(spell_item_bloodsail_admiral_hat); RegisterSpellScript(spell_item_brewfest_hops); + RegisterSpellScript(spell_item_alchemists_stone); + RegisterSpellScript(spell_item_darkmoon_card_greatness); + RegisterSpellScript(spell_item_death_choice); + RegisterSpellScriptWithArgs(spell_item_lightning_capacitor, "spell_item_lightning_capacitor"); + RegisterSpellScriptWithArgs(spell_item_thunder_capacitor, "spell_item_thunder_capacitor"); + RegisterSpellScriptWithArgs(spell_item_toc25_caster_trinket_normal, "spell_item_toc25_caster_trinket_normal"); + RegisterSpellScriptWithArgs(spell_item_toc25_caster_trinket_heroic, "spell_item_toc25_caster_trinket_heroic"); + RegisterSpellScript(spell_item_soul_preserver); + RegisterSpellScript(spell_item_charm_witch_doctor); + RegisterSpellScript(spell_item_lifegiving_gem); + RegisterSpellScript(spell_item_mana_drain); + RegisterSpellScript(spell_item_mind_control_cap); + RegisterSpellScript(spell_item_hourglass_sand); + RegisterSpellScript(spell_item_ultrasafe_transporter); + RegisterSpellScript(spell_item_power_circle); + RegisterSpellScript(spell_item_thrallmar_and_honor_hold_favor); + RegisterSpellScript(spell_item_drums_of_forgotten_kings); + RegisterSpellScript(spell_item_drums_of_the_wild); + RegisterSpellScript(spell_item_darkmoon_card_illusion); + RegisterSpellScript(spell_item_mad_alchemists_potion); + RegisterSpellScript(spell_item_decahedral_dwarven_dice); + RegisterSpellScript(spell_item_aura_of_madness); + RegisterSpellScript(spell_item_dementia); + RegisterSpellScript(spell_item_deadly_precision); + RegisterSpellScript(spell_item_deadly_precision_dummy); + RegisterSpellScript(spell_item_deathbringers_will_normal); + RegisterSpellScript(spell_item_deathbringers_will_heroic); + RegisterSpellScript(spell_item_discerning_eye_beast_dummy); + RegisterSpellScript(spell_item_frozen_shadoweave); + RegisterSpellScript(spell_item_healing_touch_refund); + RegisterSpellScript(spell_item_heartpierce); + RegisterSpellScript(spell_item_heartpierce_hero); + RegisterSpellScript(spell_item_mark_of_conquest); + RegisterSpellScript(spell_item_pendant_of_the_violet_eye); + RegisterSpellScript(spell_item_persistent_shield); + RegisterSpellScript(spell_item_pet_healing); + RegisterSpellScript(spell_item_restless_strength); + RegisterSpellScript(spell_item_unstable_power); + RegisterSpellScript(spell_item_commendation_of_kaelthas); + RegisterSpellScript(spell_item_corpse_tongue_coin); + RegisterSpellScript(spell_item_corpse_tongue_coin_heroic); + RegisterSpellScript(spell_item_crystal_spire_of_karabor); + RegisterSpellScript(spell_item_soul_harvesters_charm); + RegisterSpellScript(spell_item_sunwell_exalted_caster_neck); + RegisterSpellScript(spell_item_sunwell_exalted_healer_neck); + RegisterSpellScript(spell_item_sunwell_exalted_melee_neck); + RegisterSpellScript(spell_item_sunwell_exalted_tank_neck); + RegisterSpellScript(spell_item_swift_hand_justice_dummy); + RegisterSpellScript(spell_item_tiny_abomination_in_a_jar); + RegisterSpellScript(spell_item_tiny_abomination_in_a_jar_hero); + RegisterSpellScript(spell_item_totem_of_flowing_water); + RegisterSpellScript(spell_item_petrified_twilight_scale); + RegisterSpellScript(spell_item_petrified_twilight_scale_heroic); + RegisterSpellScript(spell_item_purified_shard_of_the_scale); + RegisterSpellScript(spell_item_shiny_shard_of_the_scale); + RegisterSpellScript(spell_item_living_root_of_the_wildheart); } diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index aa1fe3b6d..f8d5db149 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -31,6 +31,9 @@ enum MageSpells { + SPELL_MAGE_ARCANE_MISSILES_R1 = 5143, + SPELL_MAGE_BLAZING_SPEED = 31643, + SPELL_MAGE_MAGIC_ABSORPTION_MANA = 29442, SPELL_MAGE_BURNOUT_TRIGGER = 44450, SPELL_MAGE_IMPROVED_BLIZZARD_CHILLED = 12486, SPELL_MAGE_COMBUSTION = 11129, @@ -42,6 +45,7 @@ enum MageSpells SPELL_MAGE_INCANTERS_ABSORBTION_TRIGGERED = 44413, SPELL_MAGE_IGNITE = 12654, SPELL_MAGE_MASTER_OF_ELEMENTS_ENERGIZE = 29077, + SPELL_MAGE_PERMAFROST_AURA = 68391, SPELL_MAGE_SQUIRREL_FORM = 32813, SPELL_MAGE_GIRAFFE_FORM = 32816, SPELL_MAGE_SERPENT_FORM = 32817, @@ -52,7 +56,28 @@ enum MageSpells SPELL_MAGE_SUMMON_WATER_ELEMENTAL_PERMANENT = 70908, SPELL_MAGE_SUMMON_WATER_ELEMENTAL_TEMPORARY = 70907, SPELL_MAGE_GLYPH_OF_BLAST_WAVE = 62126, - SPELL_MAGE_FINGERS_OF_FROST = 44543 + SPELL_MAGE_FINGERS_OF_FROST = 44543, + SPELL_MAGE_FINGERS_OF_FROST_AURASTATE_AURA = 44544, + SPELL_MAGE_ARCANE_POTENCY_RANK_1 = 57529, + SPELL_MAGE_ARCANE_POTENCY_RANK_2 = 57531, + SPELL_MAGE_EMPOWERED_FIRE_PROC = 67545, + SPELL_MAGE_T10_2P_BONUS = 70752, + SPELL_MAGE_T10_2P_BONUS_EFFECT = 70753, + SPELL_MAGE_T8_4P_BONUS = 64869, + SPELL_MAGE_HOT_STREAK_PROC = 48108, + SPELL_MAGE_CHILLED_R1 = 12484, + SPELL_MAGE_CHILLED_R2 = 12485, + SPELL_MAGE_CHILLED_R3 = 12486, + SPELL_MAGE_MANA_SURGE = 37445, + SPELL_MAGE_FROST_NOVA = 122 +}; + +enum MageSpellIcons +{ + MAGE_ICON_MAGIC_ABSORPTION = 459, + MAGE_ICON_CLEARCASTING = 212, + MAGE_ICON_PRESENCE_OF_MIND = 139, + MAGE_ICON_LIVING_BOMB = 3000 }; class spell_mage_arcane_blast : public SpellScript @@ -112,7 +137,7 @@ class spell_mage_burning_determination : public AuraScript return true; } - void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + void HandleProc(ProcEventInfo& /*eventInfo*/) { PreventDefaultAction(); GetUnitOwner()->CastSpell(GetUnitOwner(), 54748, true); @@ -121,7 +146,7 @@ class spell_mage_burning_determination : public AuraScript void Register() override { DoCheckProc += AuraCheckProcFn(spell_mage_burning_determination::CheckProc); - OnEffectProc += AuraEffectProcFn(spell_mage_burning_determination::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + OnProc += AuraProcFn(spell_mage_burning_determination::HandleProc); } }; @@ -784,44 +809,11 @@ class spell_mage_master_of_elements : public AuraScript return ValidateSpellInfo({ SPELL_MAGE_MASTER_OF_ELEMENTS_ENERGIZE }); } - bool AfterCheckProc(ProcEventInfo& eventInfo, bool isTriggeredAtSpellProcEvent) + bool CheckProc(ProcEventInfo& eventInfo) { - if (!isTriggeredAtSpellProcEvent || !eventInfo.GetActor() || !eventInfo.GetActionTarget()) - { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetSpellInfo()) return false; - } - - _spellInfo = eventInfo.GetSpellInfo(); - - bool selectCaster = false; - // Triggered spells cost no mana so we need triggering spellInfo - if (SpellInfo const* triggeredByAuraSpellInfo = eventInfo.GetTriggerAuraSpell()) - { - _spellInfo = triggeredByAuraSpellInfo; - selectCaster = true; - } - - if (!_spellInfo) - { - return false; - } - - _ticksModifier = 1; - - // If spell is periodic, mana amount is divided by tick number - if (eventInfo.GetTriggerAuraEffectIndex() >= EFFECT_0) - { - if (Unit* caster = GetCaster()) - { - if (Unit* target = (selectCaster ? eventInfo.GetActor() : eventInfo.GetActionTarget())) - { - if (AuraEffect const* aurEff = target->GetAuraEffect(_spellInfo->Id, eventInfo.GetTriggerAuraEffectIndex(), caster->GetGUID())) - { - _ticksModifier = std::max(1, aurEff->GetTotalTicks()); - } - } - } - } return true; } @@ -830,30 +822,20 @@ class spell_mage_master_of_elements : public AuraScript { PreventDefaultAction(); - if (!_spellInfo) - return; + int32 mana = eventInfo.GetDamageInfo()->GetSpellInfo()->CalcPowerCost(GetTarget(), eventInfo.GetDamageInfo()->GetSchoolMask()); + mana = CalculatePct(mana, aurEff->GetAmount()); - if (Unit* target = GetTarget()) + if (mana > 0) { - int32 mana = int32(_spellInfo->CalcPowerCost(target, eventInfo.GetSchoolMask()) / _ticksModifier); - mana = CalculatePct(mana, aurEff->GetAmount()); - - if (mana > 0) - { - target->CastCustomSpell(SPELL_MAGE_MASTER_OF_ELEMENTS_ENERGIZE, SPELLVALUE_BASE_POINT0, mana, target, true, nullptr, aurEff); - } + GetTarget()->CastCustomSpell(SPELL_MAGE_MASTER_OF_ELEMENTS_ENERGIZE, SPELLVALUE_BASE_POINT0, mana, GetTarget(), true, nullptr, aurEff); } } void Register() override { - DoAfterCheckProc += AuraAfterCheckProcFn(spell_mage_master_of_elements::AfterCheckProc); + DoCheckProc += AuraCheckProcFn(spell_mage_master_of_elements::CheckProc); OnEffectProc += AuraEffectProcFn(spell_mage_master_of_elements::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } - -private: - SpellInfo const* _spellInfo = nullptr; - uint8 _ticksModifier = 0; }; enum SilvermoonPolymorph @@ -946,125 +928,587 @@ class spell_mage_summon_water_elemental : public SpellScript } }; -#define FingersOfFrostScriptName "spell_mage_fingers_of_frost_proc_aura" -class spell_mage_fingers_of_frost_proc_aura : public AuraScript -{ PrepareAuraScript(spell_mage_fingers_of_frost_proc_aura); +// 74396 - Fingers of Frost +// Charge consumption is handled by the default proc system in PrepareProcToTrigger +// This script only handles removing the aura state aura (44544) when the buff expires +class spell_mage_fingers_of_frost : public AuraScript +{ + PrepareAuraScript(spell_mage_fingers_of_frost); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_FINGERS_OF_FROST_AURASTATE_AURA }); + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_MAGE_FINGERS_OF_FROST_AURASTATE_AURA); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_mage_fingers_of_frost::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +// -31571 - Arcane Potency +class spell_mage_arcane_potency : public AuraScript +{ + PrepareAuraScript(spell_mage_arcane_potency); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_MAGE_ARCANE_POTENCY_RANK_1, + SPELL_MAGE_ARCANE_POTENCY_RANK_2 + }); + } bool CheckProc(ProcEventInfo& eventInfo) { - if (eventInfo.GetSpellPhaseMask() != PROC_SPELL_PHASE_CAST) - { - eventInfo.SetProcChance(_chance); - } + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // Only proc on Clearcasting or Presence of Mind + if (spellInfo->SpellIconID != MAGE_ICON_CLEARCASTING && spellInfo->SpellIconID != MAGE_ICON_PRESENCE_OF_MIND) + return false; return true; } - bool AfterCheckProc(ProcEventInfo& eventInfo, bool isTriggeredAtSpellProcEvent) + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) { - if (eventInfo.GetSpellPhaseMask() != PROC_SPELL_PHASE_CAST) - { - eventInfo.ResetProcChance(); - } - - return isTriggeredAtSpellProcEvent; + PreventDefaultAction(); + uint32 spellId = GetSpellInfo()->GetRank() == 1 ? SPELL_MAGE_ARCANE_POTENCY_RANK_1 : SPELL_MAGE_ARCANE_POTENCY_RANK_2; + GetTarget()->CastSpell(GetTarget(), spellId, true, nullptr, aurEff); } - void HandleOnEffectProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + void Register() override { - if (eventInfo.GetSpellPhaseMask() == PROC_SPELL_PHASE_CAST) + DoCheckProc += AuraCheckProcFn(spell_mage_arcane_potency::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_mage_arcane_potency::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 11129 - Combustion +class spell_mage_combustion : public AuraScript +{ + PrepareAuraScript(spell_mage_combustion); + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Prevent charge consumption on non-crits + return eventInfo.GetHitMask() & PROC_EX_CRITICAL_HIT; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_combustion::CheckProc); + } +}; + +// -31656 - Empowered Fire +class spell_mage_empowered_fire : public AuraScript +{ + PrepareAuraScript(spell_mage_empowered_fire); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_EMPOWERED_FIRE_PROC }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // Only proc on Ignite + return spellInfo->Id == SPELL_MAGE_IGNITE; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* target = GetTarget(); + // Calculate mana restored: 2% of base mana (percent value comes from spell 67545 effect 0) + uint32 percent = sSpellMgr->GetSpellInfo(SPELL_MAGE_EMPOWERED_FIRE_PROC)->Effects[EFFECT_0].CalcValue(); + int32 mana = int32(CalculatePct(target->GetCreateMana(), percent)); + target->CastCustomSpell(SPELL_MAGE_EMPOWERED_FIRE_PROC, SPELLVALUE_BASE_POINT0, mana, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_empowered_fire::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_mage_empowered_fire::HandleProc, EFFECT_0, SPELL_AURA_ADD_FLAT_MODIFIER); + } +}; + +// 48108 - Hot Streak, 57761 - Fireball! +class spell_mage_gen_extra_effects : public AuraScript +{ + PrepareAuraScript(spell_mage_gen_extra_effects); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_MAGE_T8_4P_BONUS, + SPELL_MAGE_T10_2P_BONUS, + SPELL_MAGE_T10_2P_BONUS_EFFECT + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* caster = eventInfo.GetActor(); + // T8 4P bonus: prevent double proc on Arcane Missiles + if (GetSpellInfo()->Id == SPELL_MAGE_HOT_STREAK_PROC && caster->HasAura(SPELL_MAGE_T8_4P_BONUS)) { - _chance = 100.f; - _spell = eventInfo.GetProcSpell(); - _procSpellDelayMoment = std::nullopt; - - if (!_spell || _spell->GetDelayMoment() <= 0) - PreventDefaultAction(); - - if (_spell) - _procSpellDelayMoment = _spell->GetDelayMoment(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && + (spellInfo->SpellFamilyFlags[0] & 0x00000800)) // Arcane Missiles + return false; } - else - { - if (eventInfo.GetSpellPhaseMask() == PROC_SPELL_PHASE_FINISH || (_procSpellDelayMoment.value_or(0) > 0 || !eventInfo.GetDamageInfo())) - PreventDefaultAction(); + return true; + } - ResetProcState(); + void HandleProc(ProcEventInfo& eventInfo) + { + Unit* caster = eventInfo.GetActor(); + // T10 2P bonus: apply pushing the limit on proc consumption + if (caster->HasAura(SPELL_MAGE_T10_2P_BONUS)) + caster->CastSpell(caster, SPELL_MAGE_T10_2P_BONUS_EFFECT, true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_gen_extra_effects::CheckProc); + OnProc += AuraProcFn(spell_mage_gen_extra_effects::HandleProc); + } +}; + +// 56372 - Glyph of Ice Block +class spell_mage_glyph_of_ice_block : public AuraScript +{ + PrepareAuraScript(spell_mage_glyph_of_ice_block); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_FROST_NOVA }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Player* player = GetTarget()->ToPlayer(); + if (!player) + return; + + // Reset cooldowns on Frost Nova and all its ranks + SpellInfo const* frostNovaInfo = sSpellMgr->GetSpellInfo(SPELL_MAGE_FROST_NOVA); + if (!frostNovaInfo) + return; + + PlayerSpellMap const& spellMap = player->GetSpellMap(); + for (auto const& itr : spellMap) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr.first); + if (!spellInfo) + continue; + + // Frost Nova spell family flags: 0x00000040 + if (spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && + (spellInfo->SpellFamilyFlags[0] & 0x00000040)) + { + player->RemoveSpellCooldown(spellInfo->Id, true); + } } } - void HandleAfterEffectProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + void Register() override { - switch (eventInfo.GetSpellPhaseMask()) + OnEffectProc += AuraEffectProcFn(spell_mage_glyph_of_ice_block::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 56374 - Glyph of Icy Veins +class spell_mage_glyph_of_icy_veins : public AuraScript +{ + PrepareAuraScript(spell_mage_glyph_of_icy_veins); + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + + // Remove attack speed slows and haste reducting auras + target->RemoveAurasByType(SPELL_AURA_HASTE_SPELLS); + target->RemoveAurasByType(SPELL_AURA_MOD_DECREASE_SPEED); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_mage_glyph_of_icy_veins::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 56375 - Glyph of Polymorph +class spell_mage_glyph_of_polymorph : public AuraScript +{ + PrepareAuraScript(spell_mage_glyph_of_polymorph); + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetProcTarget(); + if (!target) + return; + + // Remove DoTs from target + target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE, ObjectGuid::Empty, nullptr, true); + target->RemoveAurasByType(SPELL_AURA_PERIODIC_DAMAGE_PERCENT, ObjectGuid::Empty, nullptr, true); + target->RemoveAurasByType(SPELL_AURA_PERIODIC_LEECH, ObjectGuid::Empty, nullptr, true); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_mage_glyph_of_polymorph::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -44445 - Hot Streak +class spell_mage_hot_streak : public AuraScript +{ + PrepareAuraScript(spell_mage_hot_streak); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_HOT_STREAK_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + // Non-crit - reset counter + if (!(eventInfo.GetHitMask() & PROC_EX_CRITICAL_HIT)) { - case PROC_SPELL_PHASE_HIT: _chance = 100.f; break; - case PROC_SPELL_PHASE_FINISH: ResetProcState(); break; - default: break; + _critStreak = 0; + return; + } + + // Crit - increment counter + ++_critStreak; + + // Two crits in a row - proc Hot Streak if chance succeeds + if (_critStreak >= 2) + { + _critStreak = 0; + if (roll_chance_i(aurEff->GetAmount())) + GetTarget()->CastSpell(GetTarget(), SPELL_MAGE_HOT_STREAK_PROC, true, nullptr, aurEff); } } - void ResetProcState() + void Register() override { - _chance = 0.f; - _spell = nullptr; - _procSpellDelayMoment = std::nullopt; + OnEffectProc += AuraEffectProcFn(spell_mage_hot_streak::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } - void Register() +private: + uint8 _critStreak = 0; +}; + +// -11185 - Improved Blizzard +class spell_mage_imp_blizzard : public AuraScript +{ + PrepareAuraScript(spell_mage_imp_blizzard); + + bool Validate(SpellInfo const* /*spellInfo*/) override { - DoCheckProc += AuraCheckProcFn(spell_mage_fingers_of_frost_proc_aura::CheckProc); - DoAfterCheckProc += AuraAfterCheckProcFn(spell_mage_fingers_of_frost_proc_aura::AfterCheckProc); - OnEffectProc += AuraEffectProcFn(spell_mage_fingers_of_frost_proc_aura::HandleOnEffectProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); - AfterEffectProc += AuraEffectProcFn(spell_mage_fingers_of_frost_proc_aura::HandleAfterEffectProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + return ValidateSpellInfo( + { + SPELL_MAGE_CHILLED_R1, + SPELL_MAGE_CHILLED_R2, + SPELL_MAGE_CHILLED_R3 + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + uint32 spellId; + switch (GetSpellInfo()->GetRank()) + { + case 1: spellId = SPELL_MAGE_CHILLED_R1; break; + case 2: spellId = SPELL_MAGE_CHILLED_R2; break; + case 3: spellId = SPELL_MAGE_CHILLED_R3; break; + default: return; + } + + if (Unit* target = eventInfo.GetProcTarget()) + GetTarget()->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_mage_imp_blizzard::HandleProc, EFFECT_0, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + } +}; + +// 61062, 37447 - Improved Mana Gems +class spell_mage_imp_mana_gems : public AuraScript +{ + PrepareAuraScript(spell_mage_imp_mana_gems); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_MANA_SURGE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_MAGE_MANA_SURGE, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_mage_imp_mana_gems::HandleProc, EFFECT_1, SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + } +}; + +// -44404 - Missile Barrage +class spell_mage_missile_barrage : public AuraScript +{ + PrepareAuraScript(spell_mage_missile_barrage); + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // Arcane Blast - full proc chance (100%) + // Arcane Blast spell family flags: 0x20000000 + if (spellInfo->SpellFamilyFlags[0] & 0x20000000) + return true; + + // Other spells - 50% proc chance + return roll_chance_i(50); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_missile_barrage::CheckProc); + } +}; + +// -29441 - Magic Absorption +class spell_mage_magic_absorption : public AuraScript +{ + PrepareAuraScript(spell_mage_magic_absorption); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_MAGIC_ABSORPTION_MANA }); + } + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + return GetTarget()->HasActivePowerType(POWER_MANA); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + int32 bp = CalculatePct(int32(target->GetMaxPower(POWER_MANA)), aurEff->GetAmount()); + target->CastCustomSpell(SPELL_MAGE_MAGIC_ABSORPTION_MANA, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_magic_absorption::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_mage_magic_absorption::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -31641 - Blazing Speed +class spell_mage_blazing_speed : public AuraScript +{ + PrepareAuraScript(spell_mage_blazing_speed); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_BLAZING_SPEED }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + if (Unit* target = eventInfo.GetActionTarget()) + target->CastSpell(target, SPELL_MAGE_BLAZING_SPEED, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_mage_blazing_speed::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// -5143 - Arcane Missiles +class spell_mage_arcane_missiles : public AuraScript +{ + PrepareAuraScript(spell_mage_arcane_missiles); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_T10_2P_BONUS, SPELL_MAGE_T10_2P_BONUS_EFFECT }); + } + + void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + Unit* target = GetTarget(); + if (target->HasAura(SPELL_MAGE_T10_2P_BONUS) && _canProcT10) + target->CastSpell(target, SPELL_MAGE_T10_2P_BONUS_EFFECT, true, nullptr, aurEff); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_mage_arcane_missiles::OnRemove, EFFECT_1, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); } public: - // May point to a deleted object. - // Dereferencing is unsafe unless validity is guaranteed by the caller. - Spell const* GetProcSpell() const { return _spell; } + void AllowT10Proc() { _canProcT10 = true; } private: - float _chance = 0.f; - std::optional _procSpellDelayMoment = std::nullopt; - - // May be dangling; points to memory that might no longer be valid. - Spell const* _spell = nullptr; + bool _canProcT10 = false; }; -typedef spell_mage_fingers_of_frost_proc_aura spell_mage_fingers_of_frost_proc_aura_script; - -class spell_mage_fingers_of_frost_proc : public AuraScript +// -31661 - Dragon's Breath +class spell_mage_dragon_breath : public AuraScript { - PrepareAuraScript(spell_mage_fingers_of_frost_proc); + PrepareAuraScript(spell_mage_dragon_breath); bool CheckProc(ProcEventInfo& eventInfo) { - if (Aura* aura = GetCaster()->GetAuraOfRankedSpell(SPELL_MAGE_FINGERS_OF_FROST)) - { - if (spell_mage_fingers_of_frost_proc_aura_script* script = dynamic_cast(aura->GetScriptByName(FingersOfFrostScriptName))) - { - if (Spell const* fofProcSpell = script->GetProcSpell()) - { - if (fofProcSpell == eventInfo.GetProcSpell()) - { - return false; - } - } - } - } + // Don't proc with Living Bomb explosion + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (spellInfo && spellInfo->SpellIconID == MAGE_ICON_LIVING_BOMB && spellInfo->SpellFamilyName == SPELLFAMILY_MAGE) + return false; return true; } - void Register() + void Register() override { - DoCheckProc += AuraCheckProcFn(spell_mage_fingers_of_frost_proc::CheckProc); + DoCheckProc += AuraCheckProcFn(spell_mage_dragon_breath::CheckProc); + } +}; + +// -44614 - Frostfire Bolt +class spell_mage_frostfire_bolt : public AuraScript +{ + PrepareAuraScript(spell_mage_frostfire_bolt); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_PERMAFROST_AURA }); + } + + void ApplyPermafrost(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + if (Unit* caster = GetCaster()) + caster->CastSpell(GetTarget(), SPELL_MAGE_PERMAFROST_AURA, true, nullptr, aurEff); + } + + void RemovePermafrost(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveAurasDueToSpell(SPELL_MAGE_PERMAFROST_AURA); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_mage_frostfire_bolt::ApplyPermafrost, EFFECT_0, SPELL_AURA_MOD_DECREASE_SPEED, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); + AfterEffectRemove += AuraEffectRemoveFn(spell_mage_frostfire_bolt::RemovePermafrost, EFFECT_0, SPELL_AURA_MOD_DECREASE_SPEED, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 45438 - Ice Block +class spell_mage_ice_block : public SpellScript +{ + PrepareSpellScript(spell_mage_ice_block); + + bool Validate(SpellInfo const* spellInfo) override + { + return spellInfo->ExcludeCasterAuraSpell && ValidateSpellInfo({ static_cast(spellInfo->ExcludeCasterAuraSpell) }); + } + + void TriggerHypothermia() + { + GetCaster()->CastSpell(GetCaster(), GetSpellInfo()->ExcludeCasterAuraSpell, true); + } + + void Register() override + { + AfterHit += SpellHitFn(spell_mage_ice_block::TriggerHypothermia); + } +}; + +// 44401 - Missile Barrage (proc buff) +class spell_mage_missile_barrage_proc : public AuraScript +{ + PrepareAuraScript(spell_mage_missile_barrage_proc); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MAGE_T10_2P_BONUS, SPELL_MAGE_T8_4P_BONUS, SPELL_MAGE_ARCANE_MISSILES_R1 }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* caster = eventInfo.GetActor(); + + // T8 4P bonus: chance to not consume the proc + if (AuraEffect const* aurEff = caster->GetAuraEffect(SPELL_MAGE_T8_4P_BONUS, EFFECT_0)) + if (roll_chance_i(aurEff->GetAmount())) + return false; + + return true; + } + + void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + Unit* caster = GetTarget(); + // T10 2P bonus: signal Arcane Missiles to proc the bonus when it ends + if (caster->HasAura(SPELL_MAGE_T10_2P_BONUS)) + { + if (Aura* aura = caster->GetAuraOfRankedSpell(SPELL_MAGE_ARCANE_MISSILES_R1)) + { + if (spell_mage_arcane_missiles* script = dynamic_cast(aura->GetScriptByName("spell_mage_arcane_missiles"))) + script->AllowT10Proc(); + } + } + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_mage_missile_barrage_proc::CheckProc); + AfterEffectRemove += AuraEffectRemoveFn(spell_mage_missile_barrage_proc::OnRemove, EFFECT_0, SPELL_AURA_ADD_FLAT_MODIFIER, AURA_EFFECT_HANDLE_REAL); } }; void AddSC_mage_spell_scripts() { RegisterSpellScript(spell_mage_arcane_blast); + RegisterSpellScript(spell_mage_arcane_missiles); + RegisterSpellScript(spell_mage_arcane_potency); + RegisterSpellScript(spell_mage_blazing_speed); RegisterSpellScript(spell_mage_burning_determination); RegisterSpellScript(spell_mage_molten_armor); RegisterSpellScript(spell_mage_mirror_image); @@ -1072,19 +1516,33 @@ void AddSC_mage_spell_scripts() RegisterSpellScript(spell_mage_burnout_trigger); RegisterSpellScript(spell_mage_pet_scaling); RegisterSpellScript(spell_mage_brain_freeze); + RegisterSpellScript(spell_mage_combustion); RegisterSpellScript(spell_mage_glyph_of_eternal_water); RegisterSpellScript(spell_mage_combustion_proc); + RegisterSpellScript(spell_mage_dragon_breath); + RegisterSpellScript(spell_mage_empowered_fire); + RegisterSpellScript(spell_mage_gen_extra_effects); + RegisterSpellScript(spell_mage_frostfire_bolt); + RegisterSpellScript(spell_mage_glyph_of_ice_block); + RegisterSpellScript(spell_mage_glyph_of_icy_veins); + RegisterSpellScript(spell_mage_glyph_of_polymorph); + RegisterSpellScript(spell_mage_hot_streak); + RegisterSpellScript(spell_mage_ice_barrier); + RegisterSpellScript(spell_mage_ice_block); + RegisterSpellScript(spell_mage_imp_blizzard); + RegisterSpellScript(spell_mage_imp_mana_gems); + RegisterSpellScript(spell_mage_missile_barrage); + RegisterSpellScript(spell_mage_missile_barrage_proc); RegisterSpellScript(spell_mage_blast_wave); RegisterSpellScript(spell_mage_cold_snap); RegisterSpellScript(spell_mage_fire_frost_ward); RegisterSpellScript(spell_mage_focus_magic); - RegisterSpellScript(spell_mage_ice_barrier); RegisterSpellScript(spell_mage_ignite); RegisterSpellScript(spell_mage_living_bomb); RegisterSpellScript(spell_mage_mana_shield); RegisterSpellScript(spell_mage_master_of_elements); RegisterSpellScript(spell_mage_polymorph_cast_visual); RegisterSpellScript(spell_mage_summon_water_elemental); - RegisterSpellScript(spell_mage_fingers_of_frost_proc_aura); - RegisterSpellScript(spell_mage_fingers_of_frost_proc); + RegisterSpellScript(spell_mage_fingers_of_frost); + RegisterSpellScript(spell_mage_magic_absorption); } diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 0acf87cb2..8dd6e0d59 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "GameTime.h" #include "Group.h" #include "Player.h" #include "SpellAuraEffects.h" @@ -100,7 +101,51 @@ enum PaladinSpells enum PaladinSpellIcons { - PALADIN_ICON_ID_RETRIBUTION_AURA = 555 + PALADIN_ICON_ID_RETRIBUTION_AURA = 555, + PALADIN_ICON_JUDGEMENTS_OF_THE_WISE = 3017, + PALADIN_ICON_HAMMER_OF_THE_RIGHTEOUS = 3023, + PALADIN_ICON_RIGHTEOUS_VENGEANCE = 3025, + PALADIN_ICON_SHEATH_OF_LIGHT = 3030 +}; + +enum MiscSpellIcons +{ + SPELL_ICON_ID_STRENGTH_OF_WRYNN = 1704, + SPELL_ICON_ID_HELLSCREAM_WARSONG = 937 +}; + +// Proc system triggered spell IDs +enum PaladinProcSpells +{ + SPELL_PALADIN_ILLUMINATION_ENERGIZE = 20272, + SPELL_PALADIN_JUDGEMENTS_OF_THE_WISE_MANA = 31930, + SPELL_PALADIN_REPLENISHMENT = 57669, + SPELL_PALADIN_RIGHTEOUS_VENGEANCE_DOT = 61840, + SPELL_PALADIN_SHEATH_OF_LIGHT_HOT = 54203, + SPELL_PALADIN_JUDGEMENT_OF_LIGHT_HEAL = 20267, + SPELL_PALADIN_JUDGEMENT_OF_WISDOM_MANA = 20268, + SPELL_PALADIN_SPIRITUAL_ATTUNEMENT_MANA = 31786, + SPELL_PALADIN_BEACON_OF_LIGHT_AURA = 53563, + SPELL_PALADIN_LIGHTS_BEACON = 53651, + SPELL_PALADIN_BEACON_OF_LIGHT_FLASH = 53652, + SPELL_PALADIN_BEACON_OF_LIGHT_HOLY = 53654, + SPELL_PALADIN_HOLY_LIGHT_R1 = 635, + SPELL_PALADIN_GLYPH_OF_HOLY_LIGHT_HEAL = 54968, + SPELL_PALADIN_SACRED_SHIELD = 53601, + SPELL_PALADIN_T9_HOLY_4P_BONUS = 67191, + SPELL_PALADIN_FLASH_OF_LIGHT_PROC = 66922, + SPELL_PALADIN_ENDURING_LIGHT = 40471, + SPELL_PALADIN_ENDURING_JUDGEMENT = 40472, + SPELL_PALADIN_HOLY_POWER_ARMOR = 28790, + SPELL_PALADIN_HOLY_POWER_ATTACK_POWER = 28791, + SPELL_PALADIN_HOLY_POWER_SPELL_POWER = 28793, + SPELL_PALADIN_HOLY_POWER_MP5 = 28795, + SPELL_PALADIN_HOLY_MENDING = 64891, + SPELL_PALADIN_GLYPH_OF_DIVINITY_PROC = 54986, + SPELL_PALADIN_HEART_OF_THE_CRUSADER_EFF_R1 = 21183, + SPELL_PALADIN_JUDGEMENTS_OF_THE_JUST_PROC = 68055, + SPELL_PALADIN_SACRED_SHIELD_TRIGGER = 58597, + SPELL_PALADIN_T8_HOLY_4P_BONUS = 64895 }; class spell_pal_seal_of_command_aura : public AuraScript @@ -204,10 +249,52 @@ class spell_pal_seal_of_light : public AuraScript } }; +// 58597 - Sacred Shield (absorb) +class spell_pal_sacred_shield : public AuraScript +{ + PrepareAuraScript(spell_pal_sacred_shield); + + void CalculateAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/) + { + if (Unit* caster = GetCaster()) + { + // +75.00% from sp bonus + float bonus = CalculatePct(caster->SpellBaseHealingBonusDone(GetSpellInfo()->GetSchoolMask()), 75.0f); + + // Divine Guardian is only applied at the spell healing bonus because it was already applied to the base value in CalculateSpellDamage + bonus = caster->ApplyEffectModifiers(GetSpellInfo(), aurEff->GetEffIndex(), bonus); + bonus *= caster->CalculateLevelPenalty(GetSpellInfo()); + + amount += int32(bonus); + + // Arena - Dampening + if (AuraEffect const* auraEffArenaDampening = caster->GetAuraEffect(SPELL_GENERIC_ARENA_DAMPENING, EFFECT_0)) + AddPct(amount, auraEffArenaDampening->GetAmount()); + // Battleground - Dampening + else if (AuraEffect const* auraEffBattlegroundDampening = caster->GetAuraEffect(SPELL_GENERIC_BATTLEGROUND_DAMPENING, EFFECT_0)) + AddPct(amount, auraEffBattlegroundDampening->GetAmount()); + + // ICC buff + if (AuraEffect const* auraStrengthOfWrynn = caster->GetAuraEffect(SPELL_AURA_MOD_HEALING_DONE_PERCENT, SPELLFAMILY_GENERIC, SPELL_ICON_ID_STRENGTH_OF_WRYNN, EFFECT_2)) + AddPct(amount, auraStrengthOfWrynn->GetAmount()); + else if (AuraEffect const* auraHellscreamsWarsong = caster->GetAuraEffect(SPELL_AURA_MOD_HEALING_DONE_PERCENT, SPELLFAMILY_GENERIC, SPELL_ICON_ID_HELLSCREAM_WARSONG, EFFECT_2)) + AddPct(amount, auraHellscreamsWarsong->GetAmount()); + } + } + + void Register() override + { + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_pal_sacred_shield::CalculateAmount, EFFECT_0, SPELL_AURA_SCHOOL_ABSORB); + } +}; + class spell_pal_sacred_shield_base : public AuraScript { PrepareAuraScript(spell_pal_sacred_shield_base); + static constexpr uint32 SACRED_SHIELD_ICD = 6 * IN_MILLISECONDS; + uint32 _cooldownEnd = 0; + void CalculateAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/) { if (Unit* caster = GetCaster()) @@ -275,19 +362,21 @@ class spell_pal_sacred_shield_base : public AuraScript return; } - uint32 triggered_spell_id = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell; - if (eventInfo.GetActionTarget()->HasSpellCooldown(triggered_spell_id)) + uint32 now = GameTime::GetGameTimeMS().count(); + if (_cooldownEnd > now) return; - uint32 cooldown = eventInfo.GetProcCooldown(); - int32 basepoints = aurEff->GetAmount(); + uint32 cooldown = SACRED_SHIELD_ICD; // Item - Paladin T8 Holy 4P Bonus if (Unit* caster = aurEff->GetCaster()) if (AuraEffect const* aurEffect = caster->GetAuraEffect(64895, 0)) cooldown = aurEffect->GetAmount() * IN_MILLISECONDS; - eventInfo.GetActionTarget()->AddSpellCooldown(triggered_spell_id, 0, cooldown); + _cooldownEnd = now + cooldown; + + uint32 triggered_spell_id = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell; + int32 basepoints = aurEff->GetAmount(); eventInfo.GetActionTarget()->CastCustomSpell(eventInfo.GetActionTarget(), triggered_spell_id, &basepoints, nullptr, nullptr, true, nullptr, aurEff, eventInfo.GetActionTarget()->GetGUID()); } @@ -377,6 +466,80 @@ private: } }; +// 31821 - Aura Mastery +class spell_pal_aura_mastery : public AuraScript +{ + PrepareAuraScript(spell_pal_aura_mastery); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_AURA_MASTERY_IMMUNE }); + } + + void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->CastSpell(GetTarget(), SPELL_PALADIN_AURA_MASTERY_IMMUNE, true); + } + + void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + GetTarget()->RemoveOwnedAura(SPELL_PALADIN_AURA_MASTERY_IMMUNE, GetCasterGUID()); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_pal_aura_mastery::HandleEffectApply, EFFECT_0, SPELL_AURA_ADD_PCT_MODIFIER, AURA_EFFECT_HANDLE_REAL); + AfterEffectRemove += AuraEffectRemoveFn(spell_pal_aura_mastery::HandleEffectRemove, EFFECT_0, SPELL_AURA_ADD_PCT_MODIFIER, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 64364 - Aura Mastery Immune +class spell_pal_aura_mastery_immune : public AuraScript +{ + PrepareAuraScript(spell_pal_aura_mastery_immune); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_CONCENTRACTION_AURA }); + } + + bool CheckAreaTarget(Unit* target) + { + return target->HasAura(SPELL_PALADIN_CONCENTRACTION_AURA, GetCasterGUID()); + } + + void Register() override + { + DoCheckAreaTarget += AuraCheckAreaTargetFn(spell_pal_aura_mastery_immune::CheckAreaTarget); + } +}; + +// 53563 - Beacon of Light +class spell_pal_beacon_of_light : public AuraScript +{ + PrepareAuraScript(spell_pal_beacon_of_light); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell }); + } + + void PeriodicTick(AuraEffect const* aurEff) + { + PreventDefaultAction(); + + // area aura owner casts the spell + Unit* owner = GetAura()->GetUnitOwner(); + uint32 triggerSpell = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell; + owner->CastSpell(GetTarget(), triggerSpell, true, nullptr, aurEff, owner->GetGUID()); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_pal_beacon_of_light::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } +}; + // 31884 - Avenging Wrath class spell_pal_avenging_wrath : public AuraScript { @@ -384,17 +547,27 @@ class spell_pal_avenging_wrath : public AuraScript bool Validate(SpellInfo const* /*spellInfo*/) override { - return ValidateSpellInfo({ SPELL_PALADIN_SANCTIFIED_WRATH, SPELL_PALADIN_SANCTIFIED_WRATH_TALENT_R1 }); + return ValidateSpellInfo( + { + SPELL_PALADIN_SANCTIFIED_WRATH, + SPELL_PALADIN_SANCTIFIED_WRATH_TALENT_R1, + SPELL_PALADIN_AVENGING_WRATH_MARKER, + SPELL_PALADIN_IMMUNE_SHIELD_MARKER + }); } - void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + void HandleApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); - if (AuraEffect const* aurEff = target->GetAuraEffectOfRankedSpell(SPELL_PALADIN_SANCTIFIED_WRATH_TALENT_R1, EFFECT_2)) + if (AuraEffect const* sanctifiedWrathAurEff = target->GetAuraEffectOfRankedSpell(SPELL_PALADIN_SANCTIFIED_WRATH_TALENT_R1, EFFECT_2)) { - int32 basepoints = aurEff->GetAmount(); - target->CastCustomSpell(target, SPELL_PALADIN_SANCTIFIED_WRATH, &basepoints, &basepoints, nullptr, true, nullptr, aurEff); + int32 basepoints = sanctifiedWrathAurEff->GetAmount(); + target->CastCustomSpell(target, SPELL_PALADIN_SANCTIFIED_WRATH, &basepoints, &basepoints, nullptr, true, nullptr, sanctifiedWrathAurEff); } + + target->CastSpell(target, SPELL_PALADIN_AVENGING_WRATH_MARKER, true, nullptr, aurEff); + // Blizz seems to just apply aura without bothering to cast + target->AddAura(SPELL_PALADIN_IMMUNE_SHIELD_MARKER, target); } void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) @@ -404,7 +577,7 @@ class spell_pal_avenging_wrath : public AuraScript void Register() override { - OnEffectApply += AuraEffectApplyFn(spell_pal_avenging_wrath::HandleApply, EFFECT_0, SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, AURA_EFFECT_HANDLE_REAL); + AfterEffectApply += AuraEffectApplyFn(spell_pal_avenging_wrath::HandleApply, EFFECT_0, SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, AURA_EFFECT_HANDLE_REAL); AfterEffectRemove += AuraEffectRemoveFn(spell_pal_avenging_wrath::HandleRemove, EFFECT_0, SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, AURA_EFFECT_HANDLE_REAL); } }; @@ -1126,42 +1299,238 @@ class spell_pal_seal_of_righteousness : public AuraScript } }; -// 42463 - Seal of Vengeance -// 53739 - Seal of Corruption -class spell_pal_seal_of_vengeance : public SpellScript +// -31876 - Judgements of the Wise +class spell_pal_judgements_of_the_wise : public AuraScript { - PrepareSpellScript(spell_pal_seal_of_vengeance); + PrepareAuraScript(spell_pal_judgements_of_the_wise); bool Validate(SpellInfo const* /*spellInfo*/) override { - return ValidateSpellInfo({ SPELL_PALADIN_SEAL_OF_VENGEANCE_EFFECT, SPELL_PALADIN_SEAL_OF_CORRUPTION_EFFECT }); + return ValidateSpellInfo({ SPELL_PALADIN_JUDGEMENTS_OF_THE_WISE_MANA, SPELL_PALADIN_REPLENISHMENT }); } - void HandleScriptEffect(SpellEffIndex /*effIndex*/) + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) { - Unit* target = GetExplTargetUnit(); - uint32 spellId = GetSpell()->GetSpellInfo()->Id; - uint32 auraId = (spellId == SPELL_PALADIN_SEAL_OF_VENGEANCE_EFFECT) - ? SPELL_PALADIN_HOLY_VENGEANCE - : SPELL_PALADIN_BLOOD_CORRUPTION; - int32 damage = GetHitDamage(); - uint8 stacks = 0; - - if (target) - { - Aura* aura = target->GetAura(auraId, GetCaster()->GetGUID()); - if (aura) - stacks = aura->GetStackAmount(); - - damage = ((damage * stacks) / 5); - - SetHitDamage(damage); - } + PreventDefaultAction(); + Unit* caster = GetTarget(); + caster->CastSpell(caster, SPELL_PALADIN_JUDGEMENTS_OF_THE_WISE_MANA, true, nullptr, aurEff); + caster->CastSpell(caster, SPELL_PALADIN_REPLENISHMENT, true, nullptr, aurEff); } void Register() override { - OnEffectHitTarget += SpellEffectFn(spell_pal_seal_of_vengeance::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_WEAPON_PERCENT_DAMAGE); + OnEffectProc += AuraEffectProcFn(spell_pal_judgements_of_the_wise::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -53380 - Righteous Vengeance +class spell_pal_righteous_vengeance : public AuraScript +{ + PrepareAuraScript(spell_pal_righteous_vengeance); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_RIGHTEOUS_VENGEANCE_DOT }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + // Calculate total DoT damage (percentage of crit damage), divided by 4 ticks + int32 amount = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()) / 4; + target->CastDelayedSpellWithPeriodicAmount(GetTarget(), SPELL_PALADIN_RIGHTEOUS_VENGEANCE_DOT, SPELL_AURA_PERIODIC_DAMAGE, amount); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_righteous_vengeance::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -53501 - Sheath of Light +class spell_pal_sheath_of_light : public AuraScript +{ + PrepareAuraScript(spell_pal_sheath_of_light); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_SHEATH_OF_LIGHT_HOT }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + return healInfo && healInfo->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_PALADIN_SHEATH_OF_LIGHT_HOT); + int32 amount = CalculatePct(static_cast(eventInfo.GetHealInfo()->GetHeal()), aurEff->GetAmount()); + + ASSERT(spellInfo->GetMaxTicks() > 0); + amount /= spellInfo->GetMaxTicks(); + + caster->CastCustomSpell(SPELL_PALADIN_SHEATH_OF_LIGHT_HOT, SPELLVALUE_BASE_POINT0, amount, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_sheath_of_light::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_sheath_of_light::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// 20185 - Judgement of Light (debuff on target) +class spell_pal_judgement_of_light_heal : public AuraScript +{ + PrepareAuraScript(spell_pal_judgement_of_light_heal); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_JUDGEMENT_OF_LIGHT_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* attacker = eventInfo.GetActor(); + if (!attacker) + return; + + int32 bp = int32(attacker->CountPctFromMaxHealth(aurEff->GetAmount())); + attacker->CastCustomSpell(attacker, SPELL_PALADIN_JUDGEMENT_OF_LIGHT_HEAL, &bp, nullptr, nullptr, true, nullptr, aurEff, GetCasterGUID()); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_judgement_of_light_heal::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 20186 - Judgement of Wisdom (debuff on target) +class spell_pal_judgement_of_wisdom_mana : public AuraScript +{ + PrepareAuraScript(spell_pal_judgement_of_wisdom_mana); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_JUDGEMENT_OF_WISDOM_MANA }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetActor()->getPowerType() == POWER_MANA; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* attacker = eventInfo.GetActor(); + if (!attacker) + return; + + int32 bp = int32(CalculatePct(attacker->GetCreateMana(), aurEff->GetAmount())); + attacker->CastCustomSpell(attacker, SPELL_PALADIN_JUDGEMENT_OF_WISDOM_MANA, &bp, nullptr, nullptr, true, nullptr, aurEff, GetCasterGUID()); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_judgement_of_wisdom_mana::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_judgement_of_wisdom_mana::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 31785, 33776 - Spiritual Attunement +class spell_pal_spiritual_attunement : public AuraScript +{ + PrepareAuraScript(spell_pal_spiritual_attunement); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_SPIRITUAL_ATTUNEMENT_MANA }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // If healed by another unit (not self) + if (GetTarget() == eventInfo.GetActor()) + return false; + + // Don't allow non-positive spells to proc + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell || !procSpell->IsPositive()) + return false; + + HealInfo const* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetEffectiveHeal()) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + HealInfo const* healInfo = eventInfo.GetHealInfo(); + if (!healInfo) + return; + + uint32 effectiveHeal = healInfo->GetEffectiveHeal(); + if (!effectiveHeal) + return; + + int32 bp = int32(CalculatePct(effectiveHeal, aurEff->GetAmount())); + if (bp) + GetTarget()->CastCustomSpell(SPELL_PALADIN_SPIRITUAL_ATTUNEMENT_MANA, SPELLVALUE_BASE_POINT0, bp, GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_spiritual_attunement::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_spiritual_attunement::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 54937 - Glyph of Holy Light (proc trigger) +class spell_pal_glyph_of_holy_light_proc : public AuraScript +{ + PrepareAuraScript(spell_pal_glyph_of_holy_light_proc); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_GLYPH_OF_HOLY_LIGHT_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + int32 bp = CalculatePct(int32(healInfo->GetHeal()), aurEff->GetAmount()); + GetTarget()->CastCustomSpell(SPELL_PALADIN_GLYPH_OF_HOLY_LIGHT_HEAL, SPELLVALUE_BASE_POINT0, bp, eventInfo.GetActionTarget(), true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_glyph_of_holy_light_proc::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } }; @@ -1195,13 +1564,714 @@ class spell_pal_hand_of_protection : public SpellScript } }; +// -31871 - Divine Purpose +class spell_pal_divine_purpose : public AuraScript +{ + PrepareAuraScript(spell_pal_divine_purpose); + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + // Get the EFFECT_2 aura effect for the proc chance + if (AuraEffect const* aurEff = GetEffect(EFFECT_2)) + return roll_chance_i(aurEff->GetAmount()); + return false; + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + if (Unit* target = eventInfo.GetActionTarget()) + target->RemoveAurasWithMechanic(1 << MECHANIC_STUN, AURA_REMOVE_BY_ENEMY_SPELL); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_divine_purpose::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_divine_purpose::HandleProc, EFFECT_2, SPELL_AURA_DUMMY); + } +}; + +// -53569 - Infusion of Light +class spell_pal_infusion_of_light : public AuraScript +{ + PrepareAuraScript(spell_pal_infusion_of_light); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_SACRED_SHIELD, + SPELL_PALADIN_T9_HOLY_4P_BONUS, + SPELL_PALADIN_FLASH_OF_LIGHT_PROC + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + // Flash of Light HoT on Flash of Light when Sacred Shield active + if (spellInfo->SpellFamilyFlags[0] & 0x40000000 && spellInfo->SpellIconID == 242) + { + PreventDefaultAction(); + + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + Unit* procTarget = eventInfo.GetActionTarget(); + if (procTarget && procTarget->HasAura(SPELL_PALADIN_SACRED_SHIELD)) + { + Unit* target = GetTarget(); + SpellInfo const* flashProcInfo = sSpellMgr->AssertSpellInfo(SPELL_PALADIN_FLASH_OF_LIGHT_PROC); + int32 duration = flashProcInfo->GetMaxDuration() / 1000; + int32 pct = GetSpellInfo()->Effects[EFFECT_2].CalcValue(); + if (duration <= 0) + return; + + int32 bp0 = CalculatePct(healInfo->GetHeal() / duration, pct); + + // Item - Paladin T9 Holy 4P Bonus + if (AuraEffect const* bonus = target->GetAuraEffect(SPELL_PALADIN_T9_HOLY_4P_BONUS, EFFECT_0)) + AddPct(bp0, bonus->GetAmount()); + + target->CastCustomSpell(SPELL_PALADIN_FLASH_OF_LIGHT_PROC, SPELLVALUE_BASE_POINT0, bp0, procTarget, true, nullptr, aurEff); + } + } + // but should not proc on non-critical Holy Shocks + else if ((spellInfo->SpellFamilyFlags[0] & 0x200000 || spellInfo->SpellFamilyFlags[1] & 0x10000) && !(eventInfo.GetHitMask() & PROC_EX_CRITICAL_HIT)) + PreventDefaultAction(); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_infusion_of_light::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 40470 - Paladin Tier 6 Trinket +class spell_pal_item_t6_trinket : public AuraScript +{ + PrepareAuraScript(spell_pal_item_t6_trinket); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_ENDURING_LIGHT, + SPELL_PALADIN_ENDURING_JUDGEMENT + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // Holy Light & Flash of Light + if (spellInfo->SpellFamilyFlags[0] & 0xC0000000) + { + if (!roll_chance_i(15)) + return false; + + _triggeredSpellId = SPELL_PALADIN_ENDURING_LIGHT; + return true; + } + // Judgements + else if (spellInfo->SpellFamilyFlags[0] & 0x00800000) + { + if (!roll_chance_i(50)) + return false; + + _triggeredSpellId = SPELL_PALADIN_ENDURING_JUDGEMENT; + return true; + } + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), _triggeredSpellId, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_item_t6_trinket::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_item_t6_trinket::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } + +private: + uint32 _triggeredSpellId = 0; +}; + +// 28789 - Holy Power (T3 6P Bonus) +class spell_pal_t3_6p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pal_t3_6p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_HOLY_POWER_ARMOR, + SPELL_PALADIN_HOLY_POWER_ATTACK_POWER, + SPELL_PALADIN_HOLY_POWER_SPELL_POWER, + SPELL_PALADIN_HOLY_POWER_MP5 + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + uint32 spellId; + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + switch (target->getClass()) + { + case CLASS_PALADIN: + case CLASS_PRIEST: + case CLASS_SHAMAN: + case CLASS_DRUID: + spellId = SPELL_PALADIN_HOLY_POWER_MP5; + break; + case CLASS_MAGE: + case CLASS_WARLOCK: + spellId = SPELL_PALADIN_HOLY_POWER_SPELL_POWER; + break; + case CLASS_HUNTER: + case CLASS_ROGUE: + spellId = SPELL_PALADIN_HOLY_POWER_ATTACK_POWER; + break; + case CLASS_WARRIOR: + spellId = SPELL_PALADIN_HOLY_POWER_ARMOR; + break; + default: + return; + } + + caster->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_t3_6p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 64890 - Item - Paladin T8 Holy 2P Bonus +class spell_pal_t8_2p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pal_t8_2p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_HOLY_MENDING }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + return healInfo && healInfo->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_PALADIN_HOLY_MENDING); + int32 amount = CalculatePct(static_cast(eventInfo.GetHealInfo()->GetHeal()), aurEff->GetAmount()); + + int32 maxTicks = spellInfo->GetMaxTicks(); + if (maxTicks <= 0) + return; + + amount /= maxTicks; + caster->CastCustomSpell(SPELL_PALADIN_HOLY_MENDING, SPELLVALUE_BASE_POINT0, amount, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_t8_2p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_t8_2p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -54939 - Glyph of Divinity +class spell_pal_glyph_of_divinity : public AuraScript +{ + PrepareAuraScript(spell_pal_glyph_of_divinity); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_GLYPH_OF_DIVINITY_PROC }); + } + + void OnProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || !spellInfo->HasEffect(SPELL_EFFECT_ENERGIZE)) + return; + + Unit* caster = eventInfo.GetActor(); + if (caster == eventInfo.GetActionTarget()) + return; + + int32 amount = GetSpellInfo()->Effects[EFFECT_1].CalcValue() * 2; + caster->CastCustomSpell(SPELL_PALADIN_GLYPH_OF_DIVINITY_PROC, SPELLVALUE_BASE_POINT1, amount, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_glyph_of_divinity::OnProc, EFFECT_0, SPELL_AURA_ADD_PCT_MODIFIER); + } +}; + +// -54936 - Glyph of Holy Light (dummy aura) +class spell_pal_glyph_of_holy_light_dummy : public AuraScript +{ + PrepareAuraScript(spell_pal_glyph_of_holy_light_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_GLYPH_OF_HOLY_LIGHT_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + return healInfo && healInfo->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + HealInfo* healInfo = eventInfo.GetHealInfo(); + + uint32 basePoints = healInfo->GetSpellInfo()->Effects[EFFECT_0].BasePoints + healInfo->GetSpellInfo()->Effects[EFFECT_0].DieSides; + uint32 healAmount; + if (healInfo->GetEffectiveHeal() >= basePoints) + healAmount = healInfo->GetEffectiveHeal(); + else + healAmount = healInfo->GetHeal(); + + int32 bp0 = CalculatePct(static_cast(healAmount), aurEff->GetAmount()); + eventInfo.GetActor()->CastCustomSpell(SPELL_PALADIN_GLYPH_OF_HOLY_LIGHT_HEAL, SPELLVALUE_BASE_POINT0, bp0, eventInfo.GetActionTarget(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_glyph_of_holy_light_dummy::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_glyph_of_holy_light_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -20335 - Heart of the Crusader +class spell_pal_heart_of_the_crusader : public AuraScript +{ + PrepareAuraScript(spell_pal_heart_of_the_crusader); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_HEART_OF_THE_CRUSADER_EFF_R1 }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + uint32 spellId = sSpellMgr->GetSpellWithRank(SPELL_PALADIN_HEART_OF_THE_CRUSADER_EFF_R1, GetSpellInfo()->GetRank()); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_heart_of_the_crusader::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -20234 - Improved Lay on Hands +class spell_pal_improved_lay_of_hands : public AuraScript +{ + PrepareAuraScript(spell_pal_improved_lay_of_hands); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + uint32 triggeredSpell = GetSpellInfo()->Effects[EFFECT_0].TriggerSpell; + eventInfo.GetActionTarget()->CastSpell(eventInfo.GetActionTarget(), triggeredSpell, true, nullptr, aurEff, GetTarget()->GetGUID()); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_improved_lay_of_hands::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// -53695 - Judgements of the Just +class spell_pal_judgements_of_the_just : public AuraScript +{ + PrepareAuraScript(spell_pal_judgements_of_the_just); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PALADIN_JUDGEMENTS_OF_THE_JUST_PROC }); + } + + void OnProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + GetTarget()->CastSpell(eventInfo.GetActionTarget(), SPELL_PALADIN_JUDGEMENTS_OF_THE_JUST_PROC, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_judgements_of_the_just::OnProc, EFFECT_0, SPELL_AURA_ADD_FLAT_MODIFIER); + } +}; + +// 58597 - Sacred Shield (dummy on target) +class spell_pal_sacred_shield_dummy : public AuraScript +{ + PrepareAuraScript(spell_pal_sacred_shield_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_SACRED_SHIELD_TRIGGER, + SPELL_PALADIN_T8_HOLY_4P_BONUS + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* caster = GetCaster(); + if (!caster) + return; + + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if (_cooldownEnd > now) + return; + + std::chrono::seconds cooldown(aurEff->GetAmount()); + if (AuraEffect const* bonus = caster->GetAuraEffect(SPELL_PALADIN_T8_HOLY_4P_BONUS, EFFECT_0, caster->GetGUID())) + cooldown = std::chrono::seconds(bonus->GetAmount()); + + _cooldownEnd = now + cooldown; + caster->CastSpell(GetTarget(), SPELL_PALADIN_SACRED_SHIELD_TRIGGER, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_sacred_shield_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } + + std::chrono::steady_clock::time_point _cooldownEnd = std::chrono::steady_clock::time_point::min(); +}; + +// 31801, 53736 - Seal of Vengeance/Corruption (aura proc handler) +class spell_pal_seal_of_vengeance_aura : public AuraScript +{ + PrepareAuraScript(spell_pal_seal_of_vengeance_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_HOLY_VENGEANCE, + SPELL_PALADIN_BLOOD_CORRUPTION, + SPELL_PALADIN_SEAL_OF_VENGEANCE_EFFECT, + SPELL_PALADIN_SEAL_OF_CORRUPTION_EFFECT + }); + } + + bool Load() override + { + // Seal of Vengeance = 31801, Seal of Corruption = 53736 + _isVengeance = GetSpellInfo()->Id == 31801; + return true; + } + + void HandleApplyDoT(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + if (!(eventInfo.GetTypeMask() & PROC_FLAG_DONE_MELEE_AUTO_ATTACK)) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || spellInfo->SpellIconID != PALADIN_ICON_HAMMER_OF_THE_RIGHTEOUS) + return; + } + + uint32 dotSpell = _isVengeance ? SPELL_PALADIN_HOLY_VENGEANCE : SPELL_PALADIN_BLOOD_CORRUPTION; + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), dotSpell, true, nullptr, aurEff); + } + + void HandleSeal(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + AuraEffect const* sealDot = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PALADIN, 0x00000000, 0x00000800, 0x00000000, caster->GetGUID()); + if (!sealDot) + return; + + uint8 stacks = sealDot->GetBase()->GetStackAmount(); + uint8 maxStacks = sealDot->GetSpellInfo()->StackAmount; + + uint32 damageSpell = _isVengeance ? SPELL_PALADIN_SEAL_OF_VENGEANCE_EFFECT : SPELL_PALADIN_SEAL_OF_CORRUPTION_EFFECT; + + // Scale weapon damage % by stacks (6.6% per stack, up to 33% at 5 stacks) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(damageSpell); + int32 amount = spellInfo->Effects[EFFECT_0].CalcValue(); + amount = (amount * stacks) / maxStacks; + + caster->CastCustomSpell(damageSpell, SPELLVALUE_BASE_POINT0, amount, target, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_seal_of_vengeance_aura::HandleApplyDoT, EFFECT_0, SPELL_AURA_DUMMY); + OnEffectProc += AuraEffectProcFn(spell_pal_seal_of_vengeance_aura::HandleSeal, EFFECT_0, SPELL_AURA_DUMMY); + } + +private: + bool _isVengeance = true; +}; + +// -20234 - Illumination +class spell_pal_illumination : public AuraScript +{ + PrepareAuraScript(spell_pal_illumination); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_PALADIN_HOLY_SHOCK_R1_HEALING, + SPELL_PALADIN_ILLUMINATION_ENERGIZE, + SPELL_PALADIN_HOLY_SHOCK_R1 + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + // this script is valid only for the Holy Shock procs of illumination + if (eventInfo.GetHealInfo() && eventInfo.GetHealInfo()->GetSpellInfo()) + { + SpellInfo const* originalSpell = nullptr; + + // if proc comes from the Holy Shock heal, need to get mana cost of original spell - else it's the original heal itself + if (eventInfo.GetHealInfo()->GetSpellInfo()->SpellFamilyFlags[1] & 0x00010000) + originalSpell = sSpellMgr->GetSpellInfo(sSpellMgr->GetSpellWithRank(SPELL_PALADIN_HOLY_SHOCK_R1, eventInfo.GetHealInfo()->GetSpellInfo()->GetRank())); + else + originalSpell = eventInfo.GetHealInfo()->GetSpellInfo(); + + if (originalSpell && aurEff->GetSpellInfo()) + { + Unit* target = eventInfo.GetActor(); // Paladin is the target of the energize + int32 bp = CalculatePct(static_cast(originalSpell->CalcPowerCost(target, originalSpell->GetSchoolMask())), aurEff->GetSpellInfo()->Effects[EFFECT_1].CalcValue()); + target->CastCustomSpell(target, SPELL_PALADIN_ILLUMINATION_ENERGIZE, &bp, nullptr, nullptr, true, nullptr, aurEff); + } + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pal_illumination::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 498, 642, 1022 - Divine Protection, Divine Shield, Hand of Protection +class spell_pal_immunities : public SpellScript +{ + PrepareSpellScript(spell_pal_immunities); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_FORBEARANCE, + SPELL_PALADIN_AVENGING_WRATH_MARKER, + SPELL_PALADIN_IMMUNE_SHIELD_MARKER + }); + } + + SpellCastResult CheckCast() + { + Unit* caster = GetCaster(); + + // for HoP + Unit* target = GetExplTargetUnit(); + if (!target) + target = caster; + + // "Cannot be used within $61987d. of using Avenging Wrath." + if (target->HasAura(SPELL_PALADIN_FORBEARANCE) || target->HasAura(SPELL_PALADIN_AVENGING_WRATH_MARKER)) + return SPELL_FAILED_TARGET_AURASTATE; + + return SPELL_CAST_OK; + } + + void TriggerDebuffs() + { + if (Unit* target = GetHitUnit()) + { + // Blizz seems to just apply aura without bothering to cast + GetCaster()->AddAura(SPELL_PALADIN_FORBEARANCE, target); + GetCaster()->AddAura(SPELL_PALADIN_AVENGING_WRATH_MARKER, target); + GetCaster()->AddAura(SPELL_PALADIN_IMMUNE_SHIELD_MARKER, target); + } + } + + void Register() override + { + OnCheckCast += SpellCheckCastFn(spell_pal_immunities::CheckCast); + AfterHit += SpellHitFn(spell_pal_immunities::TriggerDebuffs); + } +}; + +// -20254 - Improved Concentration Aura +// -20138 - Improved Devotion Aura +// 31869 - Sanctified Retribution +// -53379 - Swift Retribution +class spell_pal_improved_aura : public AuraScript +{ + PrepareAuraScript(spell_pal_improved_aura); + +public: + spell_pal_improved_aura(uint32 spellId) : AuraScript(), _spellId(spellId) { } + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + _spellId, + SPELL_PALADIN_SANCTIFIED_RETRIBUTION_R1, + SPELL_PALADIN_SWIFT_RETRIBUTION_R1 + }); + } + + void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + Unit* target = GetTarget(); + target->RemoveOwnedAura(_spellId, GetCasterGUID()); // need to remove to reapply spellmods + target->CastSpell(target, _spellId, true); + } + + void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + uint32 spellId = GetSpellInfo()->GetFirstRankSpell()->Id; + + if ((spellId == SPELL_PALADIN_SANCTIFIED_RETRIBUTION_R1 && GetTarget()->GetAuraOfRankedSpell(SPELL_PALADIN_SWIFT_RETRIBUTION_R1)) + || (spellId == SPELL_PALADIN_SWIFT_RETRIBUTION_R1 && GetTarget()->GetAuraOfRankedSpell(SPELL_PALADIN_SANCTIFIED_RETRIBUTION_R1))) + return; + + GetTarget()->RemoveOwnedAura(_spellId, GetCasterGUID()); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_pal_improved_aura::HandleEffectApply, EFFECT_FIRST_FOUND, SPELL_AURA_ADD_FLAT_MODIFIER, AURA_EFFECT_HANDLE_REAL); + AfterEffectRemove += AuraEffectRemoveFn(spell_pal_improved_aura::HandleEffectRemove, EFFECT_FIRST_FOUND, SPELL_AURA_ADD_FLAT_MODIFIER, AURA_EFFECT_HANDLE_REAL); + } + +private: + uint32 _spellId; +}; + +// 53651 - Light's Beacon - Beacon of Light +class spell_pal_light_s_beacon : public AuraScript +{ + PrepareAuraScript(spell_pal_light_s_beacon); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PALADIN_BEACON_OF_LIGHT_AURA, + SPELL_PALADIN_BEACON_OF_LIGHT_FLASH, + SPELL_PALADIN_BEACON_OF_LIGHT_HOLY, + SPELL_PALADIN_HOLY_LIGHT_R1 + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Don't proc if the heal target is the beacon target (no double heal) + if (GetTarget()->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT_AURA, eventInfo.GetActor()->GetGUID())) + return false; + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return; + + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + // Holy Light heals for 100%, Flash of Light heals for 50% + uint32 healSpellId = procSpell->IsRankOf(sSpellMgr->AssertSpellInfo(SPELL_PALADIN_HOLY_LIGHT_R1)) ? + SPELL_PALADIN_BEACON_OF_LIGHT_FLASH : SPELL_PALADIN_BEACON_OF_LIGHT_HOLY; + int32 heal = CalculatePct(healInfo->GetHeal(), aurEff->GetAmount()); + + Unit* beaconTarget = GetCaster(); + if (!beaconTarget || !beaconTarget->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT_AURA, eventInfo.GetActor()->GetGUID())) + return; + + eventInfo.GetActor()->CastCustomSpell(healSpellId, SPELLVALUE_BASE_POINT0, heal, beaconTarget, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pal_light_s_beacon::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pal_light_s_beacon::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_paladin_spell_scripts() { RegisterSpellAndAuraScriptPair(spell_pal_seal_of_command, spell_pal_seal_of_command_aura); RegisterSpellScript(spell_pal_divine_intervention); + RegisterSpellScript(spell_pal_divine_purpose); RegisterSpellScript(spell_pal_seal_of_light); + RegisterSpellScript(spell_pal_sacred_shield); RegisterSpellScript(spell_pal_sacred_shield_base); RegisterSpellScript(spell_pal_ardent_defender); + RegisterSpellScript(spell_pal_aura_mastery); + RegisterSpellScript(spell_pal_aura_mastery_immune); + RegisterSpellScript(spell_pal_beacon_of_light); RegisterSpellScript(spell_pal_avenging_wrath); RegisterSpellScript(spell_pal_blessing_of_faith); RegisterSpellScript(spell_pal_blessing_of_sanctuary); @@ -1215,6 +2285,8 @@ void AddSC_paladin_spell_scripts() RegisterSpellAndAuraScriptPair(spell_pal_hand_of_sacrifice, spell_pal_hand_of_sacrifice_aura); RegisterSpellScript(spell_pal_hand_of_salvation); RegisterSpellScript(spell_pal_holy_shock); + RegisterSpellScript(spell_pal_infusion_of_light); + RegisterSpellScript(spell_pal_item_t6_trinket); RegisterSpellScriptWithArgs(spell_pal_judgement, "spell_pal_judgement_of_justice", SPELL_PALADIN_JUDGEMENT_OF_JUSTICE); RegisterSpellScriptWithArgs(spell_pal_judgement, "spell_pal_judgement_of_light", SPELL_PALADIN_JUDGEMENT_OF_LIGHT); RegisterSpellScriptWithArgs(spell_pal_judgement, "spell_pal_judgement_of_wisdom", SPELL_PALADIN_JUDGEMENT_OF_WISDOM); @@ -1222,6 +2294,29 @@ void AddSC_paladin_spell_scripts() RegisterSpellScript(spell_pal_lay_on_hands); RegisterSpellScript(spell_pal_righteous_defense); RegisterSpellScript(spell_pal_seal_of_righteousness); - RegisterSpellScript(spell_pal_seal_of_vengeance); + RegisterSpellScriptWithArgs(spell_pal_seal_of_vengeance_aura, "spell_pal_seal_of_vengeance"); + RegisterSpellScriptWithArgs(spell_pal_seal_of_vengeance_aura, "spell_pal_seal_of_corruption"); RegisterSpellScript(spell_pal_hand_of_protection); + RegisterSpellScript(spell_pal_judgements_of_the_wise); + RegisterSpellScript(spell_pal_righteous_vengeance); + RegisterSpellScript(spell_pal_sheath_of_light); + RegisterSpellScript(spell_pal_judgement_of_light_heal); + RegisterSpellScript(spell_pal_judgement_of_wisdom_mana); + RegisterSpellScript(spell_pal_spiritual_attunement); + RegisterSpellScript(spell_pal_glyph_of_holy_light_proc); + RegisterSpellScript(spell_pal_t3_6p_bonus); + RegisterSpellScript(spell_pal_t8_2p_bonus); + RegisterSpellScript(spell_pal_glyph_of_divinity); + RegisterSpellScript(spell_pal_glyph_of_holy_light_dummy); + RegisterSpellScript(spell_pal_heart_of_the_crusader); + RegisterSpellScript(spell_pal_improved_lay_of_hands); + RegisterSpellScript(spell_pal_judgements_of_the_just); + RegisterSpellScript(spell_pal_sacred_shield_dummy); + RegisterSpellScript(spell_pal_illumination); + RegisterSpellScript(spell_pal_immunities); + RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_improved_concentraction_aura", SPELL_PALADIN_IMPROVED_CONCENTRACTION_AURA); + RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_improved_devotion_aura", SPELL_PALADIN_IMPROVED_DEVOTION_AURA); + RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_sanctified_retribution", SPELL_PALADIN_SANCTIFIED_RETRIBUTION_AURA); + RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_swift_retribution", SPELL_PALADIN_SANCTIFIED_RETRIBUTION_AURA); + RegisterSpellScript(spell_pal_light_s_beacon); } diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp index a4b3c71ae..a5df31868 100644 --- a/src/server/scripts/Spells/spell_priest.cpp +++ b/src/server/scripts/Spells/spell_priest.cpp @@ -64,6 +64,24 @@ enum PriestSpellIcons PRIEST_ICON_ID_BORROWED_TIME = 2899, PRIEST_ICON_ID_EMPOWERED_RENEW_TALENT = 3021, PRIEST_ICON_ID_PAIN_AND_SUFFERING = 2874, + PRIEST_ICON_ID_BODY_AND_SOUL = 2218 +}; + +// Proc system triggered spells +enum PriestProcSpells +{ + SPELL_PRIEST_VAMPIRIC_EMBRACE_HEAL = 15290, + SPELL_PRIEST_GLYPH_OF_DISPEL_MAGIC_HEAL = 56131, + SPELL_PRIEST_BODY_AND_SOUL_SPEED = 64136, + SPELL_PRIEST_ORACULAR_HEAL = 26170, + SPELL_PRIEST_DIVINE_BLESSING = 40440, + SPELL_PRIEST_DIVINE_WRATH = 40441, + SPELL_PRIEST_ARMOR_OF_FAITH = 28810, + SPELL_PRIEST_BLESSED_HEALING = 70772, + SPELL_PRIEST_SHADOW_WORD_DEATH_R1 = 32379, + SPELL_PRIEST_MIND_BLAST_R1 = 8092, + SPELL_PRIEST_MIND_FLAY_DAMAGE = 58381, + SPELL_PRIEST_BLESSED_RECOVERY_R1 = 27813 }; enum Mics @@ -431,6 +449,27 @@ class spell_pri_lightwell_renew : public AuraScript } } + void InitializeAmount(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + // Attacks done to you equal to 30% of your total health will cancel the effect + _remainingAmount = GetTarget()->CountPctFromMaxHealth(30); + } + + void CheckDropCharge(ProcEventInfo& eventInfo) + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo) + return; + + uint32 damage = damageInfo->GetDamage(); + if (_remainingAmount <= damage) + return; + + _remainingAmount -= damage; + // prevent drop charge + PreventDefaultAction(); + } + void HandleUpdateSpellclick(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (Unit* caster = GetCaster()) @@ -448,10 +487,15 @@ class spell_pri_lightwell_renew : public AuraScript void Register() override { + DoPrepareProc += AuraProcFn(spell_pri_lightwell_renew::CheckDropCharge); DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_pri_lightwell_renew::CalculateAmount, EFFECT_0, SPELL_AURA_PERIODIC_HEAL); + AfterEffectApply += AuraEffectApplyFn(spell_pri_lightwell_renew::InitializeAmount, EFFECT_0, SPELL_AURA_PERIODIC_HEAL, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); AfterEffectApply += AuraEffectApplyFn(spell_pri_lightwell_renew::HandleUpdateSpellclick, EFFECT_0, SPELL_AURA_PERIODIC_HEAL, AURA_EFFECT_HANDLE_REAL); AfterEffectRemove += AuraEffectRemoveFn(spell_pri_lightwell_renew::HandleUpdateSpellclick, EFFECT_0, SPELL_AURA_PERIODIC_HEAL, AURA_EFFECT_HANDLE_REAL); } + +private: + uint32 _remainingAmount = 0; }; // 8129 - Mana Burn @@ -983,6 +1027,418 @@ class spell_pri_shadowfiend_death : public AuraScript } }; +// 15286 - Vampiric Embrace +class spell_pri_vampiric_embrace : public AuraScript +{ + PrepareAuraScript(spell_pri_vampiric_embrace); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_VAMPIRIC_EMBRACE_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Not proc from Mind Sear + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return false; + + return !(procSpell->SpellFamilyFlags[1] & 0x80000); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + int32 selfHeal = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()); + int32 partyHeal = selfHeal / 5; + GetTarget()->CastCustomSpell(GetTarget(), SPELL_PRIEST_VAMPIRIC_EMBRACE_HEAL, &partyHeal, &selfHeal, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_vampiric_embrace::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_vampiric_embrace::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 55677 - Glyph of Dispel Magic +class spell_pri_glyph_of_dispel_magic : public AuraScript +{ + PrepareAuraScript(spell_pri_glyph_of_dispel_magic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_GLYPH_OF_DISPEL_MAGIC_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return false; + + // Dispel Magic shares spellfamilyflag with abolish disease - check icon + if (procSpell->SpellIconID != 74) + return false; + + Unit* target = eventInfo.GetActionTarget(); + if (!target || !target->IsFriendlyTo(GetTarget())) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + int32 bp = int32(target->CountPctFromMaxHealth(aurEff->GetAmount())); + GetTarget()->CastCustomSpell(SPELL_PRIEST_GLYPH_OF_DISPEL_MAGIC_HEAL, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_glyph_of_dispel_magic::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_glyph_of_dispel_magic::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -64127 - Body and Soul +class spell_pri_body_and_soul : public AuraScript +{ + PrepareAuraScript(spell_pri_body_and_soul); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_BODY_AND_SOUL_SPEED }); + } + + bool CheckProcTriggerSpell(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + return spellInfo && (spellInfo->SpellFamilyFlags[0] & 0x00000001) != 0; + } + + bool CheckProcDummy(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + if (eventInfo.GetActor() != eventInfo.GetActionTarget()) + return false; + + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + return spellInfo && spellInfo->Id == 552; + } + + void HandleProcDummy(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + if (roll_chance_i(aurEff->GetAmount())) + eventInfo.GetActor()->CastSpell(eventInfo.GetActor(), SPELL_PRIEST_BODY_AND_SOUL_SPEED, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckEffectProc += AuraCheckEffectProcFn(spell_pri_body_and_soul::CheckProcTriggerSpell, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + DoCheckEffectProc += AuraCheckEffectProcFn(spell_pri_body_and_soul::CheckProcDummy, EFFECT_1, SPELL_AURA_DUMMY); + OnEffectProc += AuraEffectProcFn(spell_pri_body_and_soul::HandleProcDummy, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// 47569, 47570 - Improved Shadowform +class spell_pri_improved_shadowform : public AuraScript +{ + PrepareAuraScript(spell_pri_improved_shadowform); + + bool CheckProc(ProcEventInfo& /*eventInfo*/) + { + return roll_chance_i(GetEffect(EFFECT_0)->GetAmount()); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->RemoveMovementImpairingAuras(true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_improved_shadowform::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_improved_shadowform::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 26169 - AQ 3P Bonus +class spell_pri_aq_3p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pri_aq_3p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_ORACULAR_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (eventInfo.GetActor() == eventInfo.GetActionTarget()) + return false; + + HealInfo* healInfo = eventInfo.GetHealInfo(); + return healInfo && healInfo->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + int32 bp0 = CalculatePct(eventInfo.GetHealInfo()->GetHeal(), 10); + eventInfo.GetActor()->CastCustomSpell(SPELL_PRIEST_ORACULAR_HEAL, SPELLVALUE_BASE_POINT0, bp0, eventInfo.GetActor(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_aq_3p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_aq_3p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -47569 - Improved Shadowform (talent) +class spell_pri_imp_shadowform : public AuraScript +{ + PrepareAuraScript(spell_pri_imp_shadowform); + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + if (roll_chance_i(aurEff->GetAmount())) + GetTarget()->RemoveMovementImpairingAuras(true); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pri_imp_shadowform::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -15337 - Improved Spirit Tap +class spell_pri_improved_spirit_tap : public AuraScript +{ + PrepareAuraScript(spell_pri_improved_spirit_tap); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PRIEST_SHADOW_WORD_DEATH_R1, + SPELL_PRIEST_MIND_BLAST_R1 + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) + { + if (spellInfo->IsRankOf(sSpellMgr->AssertSpellInfo(SPELL_PRIEST_SHADOW_WORD_DEATH_R1)) || + spellInfo->IsRankOf(sSpellMgr->AssertSpellInfo(SPELL_PRIEST_MIND_BLAST_R1))) + return true; + else if (spellInfo->Id == SPELL_PRIEST_MIND_FLAY_DAMAGE) + return roll_chance_i(50); + } + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_improved_spirit_tap::CheckProc); + } +}; + +// 40438 - Priest Tier 6 Trinket +class spell_pri_item_t6_trinket : public AuraScript +{ + PrepareAuraScript(spell_pri_item_t6_trinket); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_PRIEST_DIVINE_BLESSING, + SPELL_PRIEST_DIVINE_WRATH + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + if (eventInfo.GetSpellTypeMask() & PROC_SPELL_TYPE_HEAL) + caster->CastSpell(caster, SPELL_PRIEST_DIVINE_BLESSING, true, nullptr, aurEff); + + if (eventInfo.GetSpellTypeMask() & PROC_SPELL_TYPE_DAMAGE) + caster->CastSpell(caster, SPELL_PRIEST_DIVINE_WRATH, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pri_item_t6_trinket::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 28809 - T3 4P Bonus +class spell_pri_t3_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pri_t3_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_ARMOR_OF_FAITH }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), SPELL_PRIEST_ARMOR_OF_FAITH, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pri_t3_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 37594 - Greater Heal Refund / T5 2P Bonus +class spell_pri_t5_heal_2p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pri_t5_heal_2p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_ITEM_EFFICIENCY }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (HealInfo* healInfo = eventInfo.GetHealInfo()) + if (Unit* healTarget = healInfo->GetTarget()) + if (healInfo->GetEffectiveHeal()) + if (healTarget->GetHealth() >= healTarget->GetMaxHealth()) + return true; + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_PRIEST_ITEM_EFFICIENCY, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_t5_heal_2p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_t5_heal_2p_bonus::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 70770 - Item - Priest T10 Healer 2P Bonus +class spell_pri_t10_heal_2p_bonus : public AuraScript +{ + PrepareAuraScript(spell_pri_t10_heal_2p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_BLESSED_HEALING }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + HealInfo* healInfo = eventInfo.GetHealInfo(); + return healInfo && healInfo->GetHeal(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_PRIEST_BLESSED_HEALING); + int32 amount = CalculatePct(static_cast(eventInfo.GetHealInfo()->GetHeal()), aurEff->GetAmount()); + + ASSERT(spellInfo->GetMaxTicks() > 0); + amount /= spellInfo->GetMaxTicks(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + caster->CastCustomSpell(target, SPELL_PRIEST_BLESSED_HEALING, &amount, nullptr, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_pri_t10_heal_2p_bonus::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_pri_t10_heal_2p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -47580 - Pain and Suffering (dummy aura) +class spell_pri_pain_and_suffering_dummy : public AuraScript +{ + PrepareAuraScript(spell_pri_pain_and_suffering_dummy); + + bool CheckDummy(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + return false; + } + + void Register() override + { + DoCheckEffectProc += AuraCheckEffectProcFn(spell_pri_pain_and_suffering_dummy::CheckDummy, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// -27811 - Blessed Recovery +class spell_pri_blessed_recovery : public AuraScript +{ + PrepareAuraScript(spell_pri_blessed_recovery); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_PRIEST_BLESSED_RECOVERY_R1 }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + DamageInfo* dmgInfo = eventInfo.GetDamageInfo(); + if (!dmgInfo || !dmgInfo->GetDamage()) + return; + + Unit* target = eventInfo.GetActionTarget(); + uint32 triggerSpell = sSpellMgr->GetSpellWithRank(SPELL_PRIEST_BLESSED_RECOVERY_R1, aurEff->GetSpellInfo()->GetRank()); + SpellInfo const* triggerInfo = sSpellMgr->AssertSpellInfo(triggerSpell); + + int32 bp = CalculatePct(static_cast(dmgInfo->GetDamage()), aurEff->GetAmount()); + + ASSERT(triggerInfo->GetMaxTicks() > 0); + bp /= triggerInfo->GetMaxTicks(); + + target->CastCustomSpell(target, triggerSpell, &bp, nullptr, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_pri_blessed_recovery::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_priest_spell_scripts() { RegisterSpellScript(spell_pri_shadowfiend_scaling); @@ -998,6 +1454,7 @@ void AddSC_priest_spell_scripts() RegisterSpellScript(spell_pri_mana_burn); RegisterSpellScript(spell_pri_mana_leech); RegisterSpellScript(spell_pri_mind_sear); + RegisterSpellScript(spell_pri_pain_and_suffering_dummy); RegisterSpellScript(spell_pri_pain_and_suffering_proc); RegisterSpellScript(spell_pri_penance); RegisterSpellAndAuraScriptPair(spell_pri_power_word_shield, spell_pri_power_word_shield_aura); @@ -1008,4 +1465,17 @@ void AddSC_priest_spell_scripts() RegisterSpellScript(spell_pri_mind_control); RegisterSpellScript(spell_pri_t4_4p_bonus); RegisterSpellScript(spell_pri_shadowfiend_death); + RegisterSpellScript(spell_pri_vampiric_embrace); + RegisterSpellScript(spell_pri_glyph_of_dispel_magic); + RegisterSpellScript(spell_pri_body_and_soul); + RegisterSpellScript(spell_pri_improved_shadowform); + // Proc system scripts + RegisterSpellScript(spell_pri_aq_3p_bonus); + RegisterSpellScript(spell_pri_blessed_recovery); + RegisterSpellScript(spell_pri_imp_shadowform); + RegisterSpellScript(spell_pri_improved_spirit_tap); + RegisterSpellScript(spell_pri_item_t6_trinket); + RegisterSpellScript(spell_pri_t3_4p_bonus); + RegisterSpellScript(spell_pri_t5_heal_2p_bonus); + RegisterSpellScript(spell_pri_t10_heal_2p_bonus); } diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 53e661a83..925bd78c4 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -18,6 +18,7 @@ #include "AreaDefines.h" #include "CellImpl.h" #include "CreatureScript.h" +#include "GameTime.h" #include "GridNotifiers.h" #include "SpellAuraEffects.h" #include "SpellMgr.h" @@ -43,6 +44,22 @@ enum RogueSpells SPELL_ROGUE_SHIV_TRIGGERED = 5940, SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST = 57933, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628, + // Proc system spells + SPELL_ROGUE_MASTER_OF_SUBTLETY_DAMAGE = 31665, + SPELL_ROGUE_DEADLY_BREW_POISON = 3409, + SPELL_ROGUE_QUICK_RECOVERY_ENERGY = 31663, + SPELL_ROGUE_TURN_THE_TABLES_R1 = 52910, + SPELL_ROGUE_TURN_THE_TABLES_R2 = 52914, + SPELL_ROGUE_TURN_THE_TABLES_R3 = 52915, + SPELL_ROGUE_OVERKILL_TRIGGERED = 58427 +}; + +enum RogueSpellIcons +{ + ROGUE_ICON_MASTER_OF_SUBTLETY = 2114, + ROGUE_ICON_CUT_TO_THE_CHASE = 2909, + ROGUE_ICON_DEADLY_BREW = 2963, + ROGUE_ICON_QUICK_RECOVERY = 2116 }; class spell_rog_savage_combat : public AuraScript @@ -753,6 +770,206 @@ class spell_rog_vanish : public SpellScript } }; +// 56800 - Glyph of Backstab +class spell_rog_glyph_of_backstab : public AuraScript +{ + PrepareAuraScript(spell_rog_glyph_of_backstab); + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + // Try to find Rupture on target + if (AuraEffect* ruptureEff = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x100000, 0, 0, GetTarget()->GetGUID())) + { + Aura* rupture = ruptureEff->GetBase(); + if (!rupture->IsRemoved() && rupture->GetDuration() > 0) + { + // Check if we can extend (max 5 seconds extension per glyph) + if ((rupture->GetApplyTime() + rupture->GetMaxDuration() / 1000 + 5) > (GameTime::GetGameTime().count() + rupture->GetDuration() / 1000)) + { + rupture->SetDuration(rupture->GetDuration() + aurEff->GetAmount() * IN_MILLISECONDS); + } + } + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_rog_glyph_of_backstab::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 31666 - Master of Subtlety +// 58428 - Overkill +template +class spell_rog_stealth_buff_tracker : public AuraScript +{ + PrepareAuraScript(spell_rog_stealth_buff_tracker); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ RemoveSpellId }); + } + + void AfterApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + if (Aura* visualAura = GetTarget()->GetAura(RemoveSpellId)) + { + int32 duration = aurEff->GetBase()->GetDuration(); + visualAura->SetDuration(duration); + visualAura->SetMaxDuration(duration); + } + } + + void PeriodicTick(AuraEffect const* /*aurEff*/) + { + GetTarget()->RemoveAurasDueToSpell(RemoveSpellId); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_rog_stealth_buff_tracker::AfterApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL); + OnEffectPeriodic += AuraEffectPeriodicFn(spell_rog_stealth_buff_tracker::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// -51664 - Cut to the Chase +class spell_rog_cut_to_the_chase : public AuraScript +{ + PrepareAuraScript(spell_rog_cut_to_the_chase); + + void HandleProc(ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + // Refresh Slice and Dice to 5 combo point max duration + if (AuraEffect const* snDEffect = GetTarget()->GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000, 0, 0)) + { + snDEffect->GetBase()->SetDuration(snDEffect->GetSpellInfo()->GetMaxDuration(), true); + } + } + + void Register() override + { + OnProc += AuraProcFn(spell_rog_cut_to_the_chase::HandleProc); + } +}; + +// -51625 - Deadly Brew +class spell_rog_deadly_brew : public AuraScript +{ + PrepareAuraScript(spell_rog_deadly_brew); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_ROGUE_DEADLY_BREW_POISON }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (target) + GetTarget()->CastSpell(target, SPELL_ROGUE_DEADLY_BREW_POISON, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_rog_deadly_brew::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -31244 - Quick Recovery +class spell_rog_quick_recovery : public AuraScript +{ + PrepareAuraScript(spell_rog_quick_recovery); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_ROGUE_QUICK_RECOVERY_ENERGY }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetSpellInfo() != nullptr; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + int32 energyBack = CalculatePct(static_cast(eventInfo.GetSpellInfo()->ManaCost), aurEff->GetAmount()); + if (energyBack > 0) + GetTarget()->CastCustomSpell(SPELL_ROGUE_QUICK_RECOVERY_ENERGY, SPELLVALUE_BASE_POINT0, energyBack, GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_rog_quick_recovery::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_rog_quick_recovery::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -13983 - Setup +class spell_rog_setup : public AuraScript +{ + PrepareAuraScript(spell_rog_setup); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (Player* target = GetTarget()->ToPlayer()) + if (eventInfo.GetActor() == target->GetSelectedUnit()) + return true; + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_rog_setup::CheckProc); + } +}; + +// -51627 - Turn the Tables +class spell_rog_turn_the_tables : public AuraScript +{ + PrepareAuraScript(spell_rog_turn_the_tables); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* caster = GetCaster(); + if (!caster) + return; + + caster->CastSpell(caster, GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_rog_turn_the_tables::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 52910, 52914, 52915 - Turn the Tables (proc) +class spell_rog_turn_the_tables_proc : public AuraScript +{ + PrepareAuraScript(spell_rog_turn_the_tables_proc); + + void Register() override + { + // No special handling needed - default behavior + } +}; + void AddSC_rogue_spell_scripts() { RegisterSpellScript(spell_rog_savage_combat); @@ -771,4 +988,14 @@ void AddSC_rogue_spell_scripts() RegisterSpellScript(spell_rog_pickpocket); RegisterSpellScript(spell_rog_vanish_purge); RegisterSpellScript(spell_rog_vanish); + // Proc system scripts + RegisterSpellScript(spell_rog_glyph_of_backstab); + RegisterSpellScriptWithArgs(spell_rog_stealth_buff_tracker, "spell_rog_master_of_subtlety"); + RegisterSpellScriptWithArgs(spell_rog_stealth_buff_tracker, "spell_rog_overkill"); + RegisterSpellScript(spell_rog_cut_to_the_chase); + RegisterSpellScript(spell_rog_deadly_brew); + RegisterSpellScript(spell_rog_quick_recovery); + RegisterSpellScript(spell_rog_setup); + RegisterSpellScript(spell_rog_turn_the_tables); + RegisterSpellScript(spell_rog_turn_the_tables_proc); } diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index 23e9fb2e9..03ce3b39c 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -17,6 +17,8 @@ #include "CreatureScript.h" #include "GridNotifiers.h" +#include "ObjectAccessor.h" +#include "Player.h" #include "SpellAuraEffects.h" #include "SpellMgr.h" #include "SpellScript.h" @@ -36,6 +38,8 @@ enum ShamanSpells SPELL_SHAMAN_CLEANSING_TOTEM_EFFECT = 52025, SPELL_SHAMAN_EARTH_SHIELD_HEAL = 379, SPELL_SHAMAN_ELEMENTAL_MASTERY = 16166, + SPELL_SHAMAN_ELEMENTAL_FOCUS = 16164, + SPELL_SHAMAN_ELEMENTAL_OATH = 51466, SPELL_SHAMAN_ELECTRIFIED = 64930, SPELL_SHAMAN_EXHAUSTION = 57723, SPELL_SHAMAN_FIRE_NOVA_R1 = 1535, @@ -43,6 +47,7 @@ enum ShamanSpells SPELL_SHAMAN_GLYPH_OF_EARTH_SHIELD = 63279, SPELL_SHAMAN_GLYPH_OF_HEALING_STREAM_TOTEM = 55456, SPELL_SHAMAN_GLYPH_OF_MANA_TIDE = 55441, + SPELL_SHAMAN_GLYPH_OF_STONECLAW_TOTEM = 63298, SPELL_SHAMAN_GLYPH_OF_THUNDERSTORM = 62132, SPELL_SHAMAN_ITEM_LIGHTNING_SHIELD = 23552, SPELL_SHAMAN_ITEM_LIGHTNING_SHIELD_DAMAGE = 27635, @@ -51,7 +56,10 @@ enum ShamanSpells SPELL_SHAMAN_LAVA_FLOWS_TRIGGERED_R1 = 64694, SPELL_SHAMAN_MANA_SPRING_TOTEM_ENERGIZE = 52032, SPELL_SHAMAN_MANA_TIDE_TOTEM = 39609, + SPELL_SHAMAN_NATURE_GUARDIAN = 31616, + SPELL_SHAMAN_NATURE_GUARDIAN_THREAT = 39301, SPELL_SHAMAN_SATED = 57724, + SPELL_SHAMAN_STONECLAW_TOTEM = 55277, SPELL_SHAMAN_STORM_EARTH_AND_FIRE = 51483, SPELL_SHAMAN_TOTEM_EARTHBIND_EARTHGRAB = 64695, SPELL_SHAMAN_TOTEM_EARTHBIND_TOTEM = 6474, @@ -61,12 +69,44 @@ enum ShamanSpells SPELL_SHAMAN_STORMSTRIKE = 17364, SPELL_SHAMAN_LAVA_LASH = 60103, SPELL_SHAMAN_LIGHTNING_BOLT_OVERLOAD = 45284, + SPELL_SHAMAN_TOTEMIC_MASTERY = 38437, + SPELL_SHAMAN_TIDAL_FORCE_CRIT = 55166, + SPELL_SHAMAN_TOTEMIC_POWER_MP5 = 28824, + SPELL_SHAMAN_TOTEMIC_POWER_SPELL_POWER = 28825, + SPELL_SHAMAN_TOTEMIC_POWER_ATTACK_POWER = 28826, + SPELL_SHAMAN_TOTEMIC_POWER_ARMOR = 28827, + SPELL_SHAMAN_WINDFURY_WEAPON_R1 = 8232, + SPELL_SHAMAN_WINDFURY_ATTACK_MH = 25504, + SPELL_SHAMAN_WINDFURY_ATTACK_OH = 33750, + SPELL_SHAMAN_ENERGY_SURGE = 40465, + SPELL_SHAMAN_POWER_SURGE = 40466, + SPELL_SHAMAN_FLAMETONGUE_ATTACK = 10444, + SPELL_SHAMAN_LIGHTNING_SHIELD_DAMAGE_R1 = 26364, + SPELL_SHAMAN_SHAMANISTIC_RAGE_PROC = 30824, + SPELL_SHAMAN_MAELSTROM_POWER = 70831, + SPELL_SHAMAN_T10_ENHANCEMENT_4P_BONUS = 70832, + SPELL_SHAMAN_LAVA_BURST_BONUS_DAMAGE = 71824, + SPELL_SHAMAN_TOTEM_OF_WRATH_SPELL_POWER = 63283, }; enum ShamanSpellIcons { SHAMAN_ICON_ID_RESTORATIVE_TOTEMS = 338, - SHAMAN_ICON_ID_SHAMAN_LAVA_FLOW = 3087 + SHAMAN_ICON_ID_SHAMAN_LAVA_FLOW = 3087, + SHAMAN_ICON_FROZEN_POWER = 3780, + SHAMAN_ICON_LIGHTNING_OVERLOAD = 2018, + SHAMAN_ICON_ID_TOTEM_OF_WRATH = 2019 +}; + +// Proc system triggered spells +enum ShamanProcSpells +{ + SPELL_SHAMAN_GLYPH_OF_HEALING_WAVE_HEAL = 55533, + SPELL_SHAMAN_SPIRIT_HUNT_HEAL = 58879, + SPELL_SHAMAN_FROZEN_POWER_ROOT = 63685, + SPELL_SHAMAN_LIGHTNING_OVERLOAD_LB = 45284, + SPELL_SHAMAN_LIGHTNING_OVERLOAD_CL = 45297, + SPELL_SHAMAN_ANCESTRAL_AWAKENING_HEAL = 52759 }; class spell_sha_totem_of_wrath : public SpellScript @@ -516,6 +556,30 @@ class spell_sha_cleansing_totem_pulse : public SpellScript } }; +// 16246 - Clearcasting +class spell_sha_clearcasting : public AuraScript +{ + PrepareAuraScript(spell_sha_clearcasting); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_ELEMENTAL_OATH }); + } + + // Elemental Oath bonus + void CalculateAmount(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/) + { + Unit const* owner = GetUnitOwner(); + if (Aura const* aura = owner->GetAuraOfRankedSpell(SPELL_SHAMAN_ELEMENTAL_OATH, owner->GetGUID())) + amount = aura->GetSpellInfo()->Effects[EFFECT_1].CalcValue(); + } + + void Register() override + { + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_sha_clearcasting::CalculateAmount, EFFECT_1, SPELL_AURA_MOD_DAMAGE_PERCENT_DONE); + } +}; + // -974 - Earth Shield class spell_sha_earth_shield : public AuraScript { @@ -920,6 +984,30 @@ class spell_sha_item_mana_surge : public AuraScript } }; +// 16164 - Elemental Focus +// Prevents weapon imbue attacks (Frostbrand, Flametongue) from proccing Clearcasting +class spell_sha_elemental_focus : public AuraScript +{ + PrepareAuraScript(spell_sha_elemental_focus); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* procSpell = eventInfo.GetSpellInfo()) + { + // Frostbrand Attack (mask0 = 0x1000000), Flametongue Attack (mask0 = 0x200000) + if (procSpell->SpellFamilyName == SPELLFAMILY_SHAMAN && + (procSpell->SpellFamilyFlags[0] & 0x1200000)) + return false; + } + return true; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_elemental_focus::CheckProc); + } +}; + // 70811 - Item - Shaman T10 Elemental 2P Bonus class spell_sha_item_t10_elemental_2p_bonus : public AuraScript { @@ -1000,6 +1088,28 @@ class spell_sha_mana_spring_totem : public SpellScript } }; +// 16191 - Mana Tide +class spell_sha_mana_tide : public AuraScript +{ + PrepareAuraScript(spell_sha_mana_tide); + + bool Validate(SpellInfo const* spellInfo) override + { + return ValidateSpellInfo({ spellInfo->Effects[EFFECT_0].TriggerSpell }); + } + + void PeriodicTick(AuraEffect const* aurEff) + { + PreventDefaultAction(); + GetTarget()->CastCustomSpell(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, SPELLVALUE_BASE_POINT0, aurEff->GetAmount(), nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_sha_mana_tide::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } +}; + // 39610 - Mana Tide Totem class spell_sha_mana_tide_totem : public SpellScript { @@ -1035,6 +1145,52 @@ class spell_sha_mana_tide_totem : public SpellScript } }; +// -30881 - Nature's Guardian +class spell_sha_nature_guardian : public AuraScript +{ + PrepareAuraScript(spell_sha_nature_guardian); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_SHAMAN_NATURE_GUARDIAN, + SPELL_SHAMAN_NATURE_GUARDIAN_THREAT + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return false; + + int32 healthpct = GetSpellInfo()->Effects[EFFECT_1].CalcValue(); + if (Unit* target = eventInfo.GetActionTarget()) + if (target->HealthBelowPctDamaged(healthpct, damageInfo->GetDamage())) + return true; + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* target = eventInfo.GetActionTarget(); + int32 bp = CalculatePct(target->GetMaxHealth(), aurEff->GetAmount()); + target->CastCustomSpell(SPELL_SHAMAN_NATURE_GUARDIAN, SPELLVALUE_BASE_POINT0, bp, target, true, nullptr, aurEff); + if (Unit* attacker = eventInfo.GetActor()) + target->CastSpell(attacker, SPELL_SHAMAN_NATURE_GUARDIAN_THREAT, true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_nature_guardian::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_nature_guardian::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + // 6495 - Sentry Totem class spell_sha_sentry_totem : public AuraScript { @@ -1058,6 +1214,42 @@ class spell_sha_sentry_totem : public AuraScript } }; +// 55278, 55328, 55329, 55330, 55332, 55333, 55335, 58589, 58590, 58591 - Stoneclaw Totem +class spell_sha_stoneclaw_totem : public SpellScript +{ + PrepareSpellScript(spell_sha_stoneclaw_totem); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_STONECLAW_TOTEM, SPELL_SHAMAN_GLYPH_OF_STONECLAW_TOTEM }); + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + Unit* target = GetHitUnit(); + + // Cast Absorb on totems + for (uint8 slot = SUMMON_SLOT_TOTEM_FIRE; slot < MAX_TOTEM_SLOT; ++slot) + { + if (!target->m_SummonSlot[slot]) + continue; + + Creature* totem = target->GetMap()->GetCreature(target->m_SummonSlot[slot]); + if (totem && totem->IsTotem()) + GetCaster()->CastCustomSpell(SPELL_SHAMAN_STONECLAW_TOTEM, SPELLVALUE_BASE_POINT0, GetEffectValue(), totem, true); + } + + // Glyph of Stoneclaw Totem + if (AuraEffect* aur = target->GetAuraEffect(SPELL_SHAMAN_GLYPH_OF_STONECLAW_TOTEM, 0)) + GetCaster()->CastCustomSpell(SPELL_SHAMAN_STONECLAW_TOTEM, SPELLVALUE_BASE_POINT0, GetEffectValue() * aur->GetAmount(), target, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_sha_stoneclaw_totem::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + // -51490 - Thunderstorm class spell_sha_thunderstorm : public SpellScript { @@ -1143,6 +1335,878 @@ class spell_sha_t8_electrified : public AuraScript } }; +// 55440 - Glyph of Healing Wave +class spell_sha_glyph_of_healing_wave : public AuraScript +{ + PrepareAuraScript(spell_sha_glyph_of_healing_wave); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_GLYPH_OF_HEALING_WAVE_HEAL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Not proc from self heals + return GetTarget() != eventInfo.GetActionTarget(); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + int32 bp = CalculatePct(int32(healInfo->GetHeal()), aurEff->GetAmount()); + GetTarget()->CastCustomSpell(SPELL_SHAMAN_GLYPH_OF_HEALING_WAVE_HEAL, SPELLVALUE_BASE_POINT0, bp, GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_glyph_of_healing_wave::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_glyph_of_healing_wave::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 58877 - Spirit Hunt +class spell_sha_spirit_hunt : public AuraScript +{ + PrepareAuraScript(spell_sha_spirit_hunt); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_SPIRIT_HUNT_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* owner = GetTarget()->GetOwner(); + if (!owner) + return; + + int32 bp = CalculatePct(int32(damageInfo->GetDamage()), aurEff->GetAmount()); + // Heal owner + GetTarget()->CastCustomSpell(SPELL_SHAMAN_SPIRIT_HUNT_HEAL, SPELLVALUE_BASE_POINT0, bp, owner, true, nullptr, aurEff); + // Heal wolf + GetTarget()->CastCustomSpell(SPELL_SHAMAN_SPIRIT_HUNT_HEAL, SPELLVALUE_BASE_POINT0, bp, GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_spirit_hunt::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -63373 - Frozen Power +class spell_sha_frozen_power : public AuraScript +{ + PrepareAuraScript(spell_sha_frozen_power); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_FROZEN_POWER_ROOT }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return false; + + // Don't proc if target is within 15 yards + if (GetTarget()->GetDistance(target) < 15.0f) + return false; + + return roll_chance_i(GetEffect(EFFECT_0)->GetAmount()); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* target = eventInfo.GetActionTarget(); + if (target) + GetTarget()->CastSpell(target, SPELL_SHAMAN_FROZEN_POWER_ROOT, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_frozen_power::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_frozen_power::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// -30675 - Lightning Overload +class spell_sha_lightning_overload : public AuraScript +{ + PrepareAuraScript(spell_sha_lightning_overload); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_LIGHTNING_OVERLOAD_LB, SPELL_SHAMAN_LIGHTNING_OVERLOAD_CL }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell || !GetTarget()->IsPlayer() || !eventInfo.GetActionTarget()) + return false; + + // Check for Lightning Bolt or Chain Lightning + if ((procSpell->SpellFamilyFlags[0] & 0x3) == 0) + return false; + + // Chain Lightning only procs 1/3 of the time + if (procSpell->SpellFamilyFlags[0] & 0x2) + return roll_chance_i(33); + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + Unit* target = eventInfo.GetActionTarget(); + if (!procSpell || !target) + return; + + uint32 spell = (procSpell->SpellFamilyFlags[0] & 0x2) ? SPELL_SHAMAN_LIGHTNING_OVERLOAD_CL : SPELL_SHAMAN_LIGHTNING_OVERLOAD_LB; + + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo) + return; + + int32 damage = damageInfo->GetDamage(); + // Half damage for critical hits + if (eventInfo.GetHitMask() & PROC_EX_CRITICAL_HIT) + damage /= 2; + // Half damage overall + damage /= 2; + + GetTarget()->CastCustomSpell(spell, SPELLVALUE_BASE_POINT0, damage, target, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_lightning_overload::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_lightning_overload::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -51556 - Ancestral Awakening (talent proc handler) +class spell_sha_ancestral_awakening : public AuraScript +{ + PrepareAuraScript(spell_sha_ancestral_awakening); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_ANCESTRAL_AWAKENING_HEAL }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + HealInfo* healInfo = eventInfo.GetHealInfo(); + if (!healInfo || !healInfo->GetHeal()) + return; + + int32 bp = CalculatePct(int32(healInfo->GetHeal()), aurEff->GetAmount()); + GetTarget()->CastCustomSpell(SPELL_SHAMAN_ANCESTRAL_AWAKENING_HEAL, SPELLVALUE_BASE_POINT0, bp, GetTarget(), true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_ancestral_awakening::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -51474 - Astral Shift aura +class spell_sha_astral_shift_aura : public AuraScript +{ + PrepareAuraScript(spell_sha_astral_shift_aura); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) + if (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_SILENCE) | (1 << MECHANIC_STUN) | (1 << MECHANIC_FEAR))) + return true; + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_astral_shift_aura::CheckProc); + } +}; + +// 52179 - Astral Shift +class spell_sha_astral_shift_visual_dummy : public AuraScript +{ + PrepareAuraScript(spell_sha_astral_shift_visual_dummy); + + void PeriodicTick(AuraEffect const* /*aurEff*/) + { + // Periodic needed to remove visual on stun/fear/silence lost + if (!(GetTarget()->GetUnitFlags() & (UNIT_FLAG_STUNNED | UNIT_FLAG_FLEEING | UNIT_FLAG_SILENCED))) + Remove(); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_sha_astral_shift_visual_dummy::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +// -10400 - Flametongue Weapon (Passive) +class spell_sha_flametongue_weapon : public AuraScript +{ + PrepareAuraScript(spell_sha_flametongue_weapon); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_FLAMETONGUE_ATTACK }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Player* player = eventInfo.GetActor()->ToPlayer(); + if (!player) + return false; + + Item* item = player->GetItemByGuid(GetAura()->GetCastItemGUID()); + if (!item || !item->IsEquipped()) + return false; + + WeaponAttackType attType = Player::GetAttackBySlot(item->GetSlot()); + if (attType != BASE_ATTACK && attType != OFF_ATTACK) + return false; + + if (((attType == BASE_ATTACK) && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK)) || + ((attType == OFF_ATTACK) && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK))) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Player* player = eventInfo.GetActor()->ToPlayer(); + Unit* target = eventInfo.GetActionTarget(); + WeaponAttackType attType = BASE_ATTACK; + if (eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK) + attType = OFF_ATTACK; + + Item* item = player->GetWeaponForAttack(attType); + if (!item) + return; + + float basePoints = GetSpellInfo()->Effects[aurEff->GetEffIndex()].CalcValue(); + + // Flametongue max damage is normalized based on a 4.0 speed weapon + float attackSpeed = player->GetAttackTime(attType) / 1000.f; + float fireDamage = basePoints / 100.0f; + fireDamage *= attackSpeed; + + // clip value between (BasePoints / 77) and (BasePoints / 25) as the tooltip indicates + RoundToInterval(fireDamage, basePoints / 77.0f, basePoints / 25.0f); + + // Calculate Spell Power scaling + float spellPowerBonus = player->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE); + spellPowerBonus += target->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_TAKEN, SPELL_SCHOOL_MASK_FIRE); + + float const spCoeff = 0.03811f; + spellPowerBonus *= spCoeff * attackSpeed; + + int32 totalDamage = int32(fireDamage + spellPowerBonus); + player->CastCustomSpell(SPELL_SHAMAN_FLAMETONGUE_ATTACK, SPELLVALUE_BASE_POINT0, totalDamage, target, true, item, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_flametongue_weapon::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_flametongue_weapon::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 63279 - Glyph of Earth Shield +class spell_sha_glyph_of_earth_shield : public AuraScript +{ + PrepareAuraScript(spell_sha_glyph_of_earth_shield); + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + SpellInfo const* earthShield = eventInfo.GetSpellInfo(); + if (!earthShield) + return; + + AuraEffect* earthShieldEffect = eventInfo.GetActionTarget()->GetAuraEffect(earthShield->Id, EFFECT_0, eventInfo.GetActor()->GetGUID()); + if (!earthShieldEffect) + return; + + int32 amount = earthShieldEffect->GetAmount(); + AddPct(amount, aurEff->GetAmount()); + earthShieldEffect->SetAmount(amount); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_glyph_of_earth_shield::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 63280 - Glyph of Totem of Wrath +class spell_sha_glyph_of_totem_of_wrath : public AuraScript +{ + PrepareAuraScript(spell_sha_glyph_of_totem_of_wrath); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_TOTEM_OF_WRATH_SPELL_POWER }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Totem of Wrath shares family flags with other totems + // filter by spellIcon instead + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo || spellInfo->SpellIconID != SHAMAN_ICON_ID_TOTEM_OF_WRATH) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + + // Fire totem summon slot + Creature* totem = ObjectAccessor::GetCreature(*caster, caster->m_SummonSlot[1]); + if (!totem) + return; + + SpellInfo const* totemSpell = sSpellMgr->GetSpellInfo(totem->m_spells[0]); + if (!totemSpell) + return; + + int32 bp0 = CalculatePct(totemSpell->Effects[EFFECT_0].CalcValue(caster), aurEff->GetAmount()); + int32 bp1 = CalculatePct(totemSpell->Effects[EFFECT_1].CalcValue(caster), aurEff->GetAmount()); + caster->CastCustomSpell(caster, SPELL_SHAMAN_TOTEM_OF_WRATH_SPELL_POWER, &bp0, &bp1, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_glyph_of_totem_of_wrath::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_glyph_of_totem_of_wrath::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -16180 - Improved Water Shield +class spell_sha_imp_water_shield : public AuraScript +{ + PrepareAuraScript(spell_sha_imp_water_shield); + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return false; + + // If we're here, we've already passed initial aura roll + // So just chance based on 100% + + // Default chance for Healing Wave and Riptide + int32 chance = 100; + // Lesser Healing Wave - 0.6 of default + if (spellInfo->SpellFamilyFlags[0] & 0x00000080) + chance = 60; + // Chain heal - 0.3 of default + else if (spellInfo->SpellFamilyFlags[0] & 0x00000100) + chance = 30; + + if (!roll_chance_i(chance)) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + AuraEffect const* waterShield = caster->GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x00000000, 0x00000020, 0x00000000, caster->GetGUID()); + if (!waterShield) + return; + + uint32 spellId = waterShield->GetSpellInfo()->Effects[waterShield->GetEffIndex()].TriggerSpell; + caster->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_imp_water_shield::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_imp_water_shield::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 40463 - Shaman Tier 6 Trinket +class spell_sha_item_t6_trinket : public AuraScript +{ + PrepareAuraScript(spell_sha_item_t6_trinket); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_SHAMAN_ENERGY_SURGE, + SPELL_SHAMAN_POWER_SURGE + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + SpellInfo const* spellInfo = eventInfo.GetSpellInfo(); + if (!spellInfo) + return; + + uint32 spellId; + int32 chance; + + // Lesser Healing Wave + if (spellInfo->SpellFamilyFlags[0] & 0x00000080) + { + spellId = SPELL_SHAMAN_ENERGY_SURGE; + chance = 10; + } + // Lightning Bolt + else if (spellInfo->SpellFamilyFlags[0] & 0x00000001) + { + spellId = SPELL_SHAMAN_ENERGY_SURGE; + chance = 15; + } + // Stormstrike + else if (spellInfo->SpellFamilyFlags[1] & 0x00000010) + { + spellId = SPELL_SHAMAN_POWER_SURGE; + chance = 50; + } + else + return; + + if (roll_chance_i(chance)) + eventInfo.GetActor()->CastSpell(nullptr, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_item_t6_trinket::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 53817 - Maelstrom Weapon +class spell_sha_maelstrom_weapon : public AuraScript +{ + PrepareAuraScript(spell_sha_maelstrom_weapon); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_SHAMAN_MAELSTROM_POWER, + SPELL_SHAMAN_T10_ENHANCEMENT_4P_BONUS + }); + } + + void HandleBonus(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (GetStackAmount() < int32(GetSpellInfo()->StackAmount)) + return; + + Unit* caster = GetUnitOwner(); + AuraEffect const* aurEff = caster->GetAuraEffect(SPELL_SHAMAN_T10_ENHANCEMENT_4P_BONUS, EFFECT_0); + if (!aurEff || !roll_chance_i(aurEff->GetAmount())) + return; + + caster->CastSpell(nullptr, SPELL_SHAMAN_MAELSTROM_POWER, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectApply += AuraEffectApplyFn(spell_sha_maelstrom_weapon::HandleBonus, EFFECT_0, SPELL_AURA_ADD_PCT_MODIFIER, AURA_EFFECT_HANDLE_CHANGE_AMOUNT); + } +}; + +// 30823 - Shamanistic Rage +class spell_sha_shamanistic_rage : public AuraScript +{ + PrepareAuraScript(spell_sha_shamanistic_rage); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_SHAMANISTIC_RAGE_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* target = GetTarget(); + int32 amount = CalculatePct(static_cast(target->GetTotalAttackPowerValue(BASE_ATTACK)), aurEff->GetAmount()); + target->CastCustomSpell(SPELL_SHAMAN_SHAMANISTIC_RAGE_PROC, SPELLVALUE_BASE_POINT0, amount, target, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_shamanistic_rage::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// -324 - Lightning Shield +class spell_sha_lightning_shield : public AuraScript +{ + PrepareAuraScript(spell_sha_lightning_shield); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_LIGHTNING_SHIELD_DAMAGE_R1 }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (eventInfo.GetActionTarget()) + return true; + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + uint32 triggerSpell = sSpellMgr->GetSpellWithRank(SPELL_SHAMAN_LIGHTNING_SHIELD_DAMAGE_R1, aurEff->GetSpellInfo()->GetRank()); + eventInfo.GetActionTarget()->CastSpell(eventInfo.GetActor(), triggerSpell, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_lightning_shield::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_lightning_shield::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// -51525 - Static Shock +class spell_sha_static_shock : public AuraScript +{ + PrepareAuraScript(spell_sha_static_shock); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_LIGHTNING_SHIELD_DAMAGE_R1 }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + AuraEffect const* lightningShield = caster->GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x00000400, 0x00000000, 0x00000000, caster->GetGUID()); + if (!lightningShield) + return; + + uint32 spellId = sSpellMgr->GetSpellWithRank(SPELL_SHAMAN_LIGHTNING_SHIELD_DAMAGE_R1, lightningShield->GetSpellInfo()->GetRank()); + eventInfo.GetActor()->CastSpell(eventInfo.GetActionTarget(), spellId, true, nullptr, aurEff); + lightningShield->GetBase()->DropCharge(); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_static_shock::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 70817 - Item - Shaman T10 Elemental 4P Bonus +class spell_sha_t10_elemental_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_sha_t10_elemental_4p_bonus); + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + // try to find spell Flame Shock on the target + AuraEffect* flameShock = target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_SHAMAN, 0x10000000, 0x00000000, 0x00000000, caster->GetGUID()); + if (!flameShock) + return; + + Aura* flameShockAura = flameShock->GetBase(); + + int32 maxDuration = flameShockAura->GetMaxDuration(); + int32 newDuration = flameShockAura->GetDuration() + aurEff->GetAmount() * IN_MILLISECONDS; + + flameShockAura->SetDuration(newDuration); + // is it blizzlike to change max duration for FS? + if (newDuration > maxDuration) + flameShockAura->SetMaxDuration(newDuration); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_t10_elemental_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 28823 - Totemic Power (T3 6P Bonus) +class spell_sha_t3_6p_bonus : public AuraScript +{ + PrepareAuraScript(spell_sha_t3_6p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_SHAMAN_TOTEMIC_POWER_ARMOR, + SPELL_SHAMAN_TOTEMIC_POWER_ATTACK_POWER, + SPELL_SHAMAN_TOTEMIC_POWER_SPELL_POWER, + SPELL_SHAMAN_TOTEMIC_POWER_MP5 + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + uint32 spellId; + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + + switch (target->getClass()) + { + case CLASS_PALADIN: + case CLASS_PRIEST: + case CLASS_SHAMAN: + case CLASS_DRUID: + spellId = SPELL_SHAMAN_TOTEMIC_POWER_MP5; + break; + case CLASS_MAGE: + case CLASS_WARLOCK: + spellId = SPELL_SHAMAN_TOTEMIC_POWER_SPELL_POWER; + break; + case CLASS_HUNTER: + case CLASS_ROGUE: + spellId = SPELL_SHAMAN_TOTEMIC_POWER_ATTACK_POWER; + break; + case CLASS_WARRIOR: + spellId = SPELL_SHAMAN_TOTEMIC_POWER_ARMOR; + break; + default: + return; + } + + caster->CastSpell(target, spellId, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_t3_6p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 28820 - Lightning Shield (The Earthshatterer 8P Bonus) +class spell_sha_t3_8p_bonus : public AuraScript +{ + PrepareAuraScript(spell_sha_t3_8p_bonus); + + void PeriodicTick(AuraEffect const* /*aurEff*/) + { + PreventDefaultAction(); + + // Need remove self if Lightning Shield not active + if (!GetTarget()->GetAuraEffect(SPELL_AURA_PROC_TRIGGER_SPELL, SPELLFAMILY_SHAMAN, 0x400, 0, 0)) + Remove(); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_sha_t3_8p_bonus::PeriodicTick, EFFECT_1, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } +}; + +// 64928 - Item - Shaman T8 Elemental 4P Bonus +class spell_sha_t8_elemental_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_sha_t8_elemental_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_ELECTRIFIED }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_SHAMAN_ELECTRIFIED); + if (!spellInfo) + return; + + int32 amount = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()); + amount /= spellInfo->GetMaxTicks(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + target->CastDelayedSpellWithPeriodicAmount(caster, SPELL_SHAMAN_ELECTRIFIED, SPELL_AURA_PERIODIC_DAMAGE, amount); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_t8_elemental_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 67228 - Item - Shaman T9 Elemental 4P Bonus (Lava Burst) +class spell_sha_t9_elemental_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_sha_t9_elemental_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_LAVA_BURST_BONUS_DAMAGE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_SHAMAN_LAVA_BURST_BONUS_DAMAGE); + if (!spellInfo) + return; + + int32 amount = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()); + amount /= spellInfo->GetMaxTicks(); + + Unit* caster = eventInfo.GetActor(); + Unit* target = eventInfo.GetActionTarget(); + target->CastDelayedSpellWithPeriodicAmount(caster, SPELL_SHAMAN_LAVA_BURST_BONUS_DAMAGE, SPELL_AURA_PERIODIC_DAMAGE, amount); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_t9_elemental_4p_bonus::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 55198 - Tidal Force +class spell_sha_tidal_force_dummy : public AuraScript +{ + PrepareAuraScript(spell_sha_tidal_force_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHAMAN_TIDAL_FORCE_CRIT }); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + eventInfo.GetActor()->RemoveAuraFromStack(SPELL_SHAMAN_TIDAL_FORCE_CRIT); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_sha_tidal_force_dummy::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -8232 - Windfury Weapon (Passive) +class spell_sha_windfury_weapon : public AuraScript +{ + PrepareAuraScript(spell_sha_windfury_weapon); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_SHAMAN_WINDFURY_WEAPON_R1, + SPELL_SHAMAN_WINDFURY_ATTACK_MH, + SPELL_SHAMAN_WINDFURY_ATTACK_OH + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + Player* player = eventInfo.GetActor()->ToPlayer(); + if (!player) + return false; + + Item* item = player->GetItemByGuid(GetAura()->GetCastItemGUID()); + if (!item || !item->IsEquipped()) + return false; + + WeaponAttackType attType = Player::GetAttackBySlot(item->GetSlot()); + if (attType != BASE_ATTACK && attType != OFF_ATTACK) + return false; + + if (((attType == BASE_ATTACK) && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK)) || + ((attType == OFF_ATTACK) && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK))) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Player* player = eventInfo.GetActor()->ToPlayer(); + Unit* target = eventInfo.GetActionTarget(); + if (!target) + return; + + Item* item = player->GetItemByGuid(GetAura()->GetCastItemGUID()); + if (!item) + return; + + uint8 slot = item->GetSlot(); + bool mainHand = slot == EQUIPMENT_SLOT_MAINHAND; + uint32 spellId = mainHand ? SPELL_SHAMAN_WINDFURY_ATTACK_MH : SPELL_SHAMAN_WINDFURY_ATTACK_OH; + + SpellInfo const* windfurySpellInfo = sSpellMgr->GetSpellInfo(SPELL_SHAMAN_WINDFURY_WEAPON_R1); + int32 bonus = windfurySpellInfo ? windfurySpellInfo->Effects[EFFECT_1].CalcValue(player) : 0; + bonus = int32(bonus * player->GetAttackTime(mainHand ? BASE_ATTACK : OFF_ATTACK) / 1000.f); + + player->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, bonus, target, true, item, aurEff); + player->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, bonus, target, true, item, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_sha_windfury_weapon::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_sha_windfury_weapon::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + void AddSC_shaman_spell_scripts() { RegisterSpellScript(spell_sha_totem_of_wrath); @@ -1167,12 +2231,40 @@ void AddSC_shaman_spell_scripts() RegisterSpellScript(spell_sha_item_lightning_shield); RegisterSpellScript(spell_sha_item_lightning_shield_trigger); RegisterSpellScript(spell_sha_item_mana_surge); + RegisterSpellScript(spell_sha_elemental_focus); RegisterSpellScript(spell_sha_item_t10_elemental_2p_bonus); RegisterSpellScript(spell_sha_lava_lash); RegisterSpellScript(spell_sha_mana_spring_totem); + RegisterSpellScript(spell_sha_mana_tide); RegisterSpellScript(spell_sha_mana_tide_totem); + RegisterSpellScript(spell_sha_nature_guardian); RegisterSpellScript(spell_sha_sentry_totem); + RegisterSpellScript(spell_sha_stoneclaw_totem); RegisterSpellScript(spell_sha_thunderstorm); RegisterSpellScript(spell_sha_flurry_proc); RegisterSpellScript(spell_sha_t8_electrified); + RegisterSpellScript(spell_sha_glyph_of_healing_wave); + RegisterSpellScript(spell_sha_spirit_hunt); + RegisterSpellScript(spell_sha_frozen_power); + RegisterSpellScript(spell_sha_lightning_overload); + RegisterSpellScript(spell_sha_ancestral_awakening); + RegisterSpellScript(spell_sha_astral_shift_aura); + RegisterSpellScript(spell_sha_astral_shift_visual_dummy); + RegisterSpellScript(spell_sha_clearcasting); + RegisterSpellScript(spell_sha_flametongue_weapon); + RegisterSpellScript(spell_sha_glyph_of_earth_shield); + RegisterSpellScript(spell_sha_glyph_of_totem_of_wrath); + RegisterSpellScript(spell_sha_imp_water_shield); + RegisterSpellScript(spell_sha_item_t6_trinket); + RegisterSpellScript(spell_sha_maelstrom_weapon); + RegisterSpellScript(spell_sha_shamanistic_rage); + RegisterSpellScript(spell_sha_lightning_shield); + RegisterSpellScript(spell_sha_static_shock); + RegisterSpellScript(spell_sha_t10_elemental_4p_bonus); + RegisterSpellScript(spell_sha_t3_6p_bonus); + RegisterSpellScript(spell_sha_t3_8p_bonus); + RegisterSpellScript(spell_sha_t8_elemental_4p_bonus); + RegisterSpellScript(spell_sha_t9_elemental_4p_bonus); + RegisterSpellScript(spell_sha_tidal_force_dummy); + RegisterSpellScript(spell_sha_windfury_weapon); } diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index e484b2b33..f424f242f 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -36,6 +36,7 @@ enum WarlockSpells { + SPELL_WARLOCK_SHADOW_TRANCE = 17941, SPELL_WARLOCK_DRAIN_SOUL_R1 = 1120, SPELL_WARLOCK_CREATE_SOULSHARD = 43836, SPELL_WARLOCK_CURSE_OF_DOOM_EFFECT = 18662, @@ -77,6 +78,22 @@ enum WarlockSpells SPELL_WARLOCK_PET_VOID_STAR_TALISMAN = 37386, // Void Star Talisman SPELL_WARLOCK_DEMONIC_PACT_PROC = 48090, SPELL_WARLOCK_GLYPH_OF_VOIDWALKER = 56247, + SPELL_WARLOCK_GLYPH_OF_LIFE_TAP_TRIGGERED = 63321, + SPELL_WARLOCK_SOUL_LEECH_HEAL = 30294, + SPELL_WARLOCK_IMP_SOUL_LEECH_R1 = 54117, + SPELL_WARLOCK_SOUL_LEECH_PET_MANA_1 = 54607, + SPELL_WARLOCK_SOUL_LEECH_PET_MANA_2 = 59118, + SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_1 = 54300, + SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_2 = 59117, + SPELL_WARLOCK_SHADOWFLAME_PROC = 37378, + SPELL_WARLOCK_FLAMESHADOW_PROC = 37379, + SPELL_REPLENISHMENT = 57669, + SPELL_WARLOCK_NETHER_PROTECTION_HOLY = 54370, + SPELL_WARLOCK_NETHER_PROTECTION_FIRE = 54371, + SPELL_WARLOCK_NETHER_PROTECTION_FROST = 54372, + SPELL_WARLOCK_NETHER_PROTECTION_ARCANE = 54373, + SPELL_WARLOCK_NETHER_PROTECTION_SHADOW = 54374, + SPELL_WARLOCK_NETHER_PROTECTION_NATURE = 54375, }; enum WarlockSpellIcons @@ -1458,8 +1475,483 @@ class spell_warl_demonic_pact_aura : public AuraScript } }; +// -980 - Curse of Agony +class spell_warl_curse_of_agony : public AuraScript +{ + PrepareAuraScript(spell_warl_curse_of_agony); + + void ApplyEffect(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + _tick_amount = aurEff->GetAmount(); + } + + void HandleEffectPeriodicUpdate(AuraEffect* aurEff) + { + switch (aurEff->GetTickNumber()) + { + // 1..4 ticks, 1/2 from normal tick damage + case 1: + aurEff->SetAmount(_tick_amount / 2); + break; + // 5..8 ticks have normal tick damage + case 5: + aurEff->SetAmount(_tick_amount); + break; + // 9..12 ticks, 3/2 from normal tick damage + case 9: + aurEff->SetAmount((_tick_amount + 1) * 3 / 2); // +1 prevent 0.5 damage possible lost at 1..4 ticks + break; + // 13 and 14 ticks (glyphed only), twice normal tick damage + case 13: + aurEff->SetAmount(_tick_amount * 2); + break; + } + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_warl_curse_of_agony::ApplyEffect, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); + OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_warl_curse_of_agony::HandleEffectPeriodicUpdate, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE); + } +private: + uint32 _tick_amount = 0; +}; + +// 56218 - Glyph of Corruption +class spell_warl_glyph_of_corruption_nightfall : public AuraScript +{ + PrepareAuraScript(spell_warl_glyph_of_corruption_nightfall); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_SHADOW_TRANCE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + Unit* caster = eventInfo.GetActor(); + caster->CastSpell(caster, SPELL_WARLOCK_SHADOW_TRANCE, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_glyph_of_corruption_nightfall::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 18094, 18095 - Nightfall +class spell_warl_nightfall : public AuraScript +{ + PrepareAuraScript(spell_warl_nightfall); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_SHADOW_TRANCE }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_WARLOCK_SHADOW_TRANCE, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_nightfall::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -63156 - Decimation +class spell_warl_decimation : public AuraScript +{ + PrepareAuraScript(spell_warl_decimation); + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) + if (eventInfo.GetActionTarget()->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, spellInfo, eventInfo.GetActor())) + return true; + + return false; + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_warl_decimation::CheckProc); + } +}; + +// 54909, 53646 - Demonic Pact +class spell_warl_demonic_pact : public AuraScript +{ + PrepareAuraScript(spell_warl_demonic_pact); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_DEMONIC_PACT_PROC }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + return eventInfo.GetActor() && eventInfo.GetActor()->IsPet(); + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + if (Unit* owner = eventInfo.GetActor()->GetOwner()) + { + if (AuraEffect* aurEff = owner->GetDummyAuraEffect(SPELLFAMILY_WARLOCK, WARLOCK_ICON_ID_DEMONIC_PACT, EFFECT_0)) + { + int32 bp = static_cast((aurEff->GetAmount() * owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) + 100.0f) / 100.0f); + owner->CastCustomSpell(owner, SPELL_WARLOCK_DEMONIC_PACT_PROC, &bp, &bp, nullptr, true, nullptr, aurEff); + } + } + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_warl_demonic_pact::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_warl_demonic_pact::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// 63320 - Glyph of Life Tap +class spell_warl_glyph_of_life_tap : public AuraScript +{ + PrepareAuraScript(spell_warl_glyph_of_life_tap); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_GLYPH_OF_LIFE_TAP_TRIGGERED }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + Unit* caster = GetTarget(); + caster->CastSpell(caster, SPELL_WARLOCK_GLYPH_OF_LIFE_TAP_TRIGGERED, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_glyph_of_life_tap::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 18213, 18372 - Improved Drain Soul (proc handler) +class spell_warl_improved_drain_soul : public AuraScript +{ + PrepareAuraScript(spell_warl_improved_drain_soul); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_WARLOCK_DRAIN_SOUL_R1, + SPELL_WARLOCK_IMPROVED_DRAIN_SOUL_R1, + SPELL_WARLOCK_IMPROVED_DRAIN_SOUL_PROC + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + // Make sure that dying unit is afflicted by the caster's Drain Soul debuff + Unit* caster = eventInfo.GetActor(); + Unit* victim = eventInfo.GetActionTarget(); + return victim->GetAuraApplicationOfRankedSpell(SPELL_WARLOCK_DRAIN_SOUL_R1, caster->GetGUID()) != nullptr; + } + + void HandleProc(ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = eventInfo.GetActor(); + // Improved Drain Soul - use the current aura (this script is on the talent) + int32 amount = CalculatePct(caster->GetMaxPower(POWER_MANA), GetSpellInfo()->Effects[EFFECT_2].CalcValue()); + caster->CastCustomSpell(SPELL_WARLOCK_IMPROVED_DRAIN_SOUL_PROC, SPELLVALUE_BASE_POINT0, amount, caster, true); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_warl_improved_drain_soul::CheckProc); + OnProc += AuraProcFn(spell_warl_improved_drain_soul::HandleProc); + } +}; + +// -27243 - Seed of Corruption (proc handler) +class spell_warl_seed_of_corruption_dummy : public AuraScript +{ + PrepareAuraScript(spell_warl_seed_of_corruption_dummy); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_R1 }); + } + + void CalculateBuffer(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/) + { + Unit* caster = GetCaster(); + if (!caster) + return; + + amount = caster->SpellDamageBonusDone(GetUnitOwner(), GetSpellInfo(), amount, SPELL_DIRECT_DAMAGE, aurEff->GetEffIndex()); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + int32 amount = aurEff->GetAmount() - damageInfo->GetDamage(); + if (amount > 0) + { + const_cast(aurEff)->SetAmount(amount); + if (!GetTarget()->HealthBelowPctDamaged(1, damageInfo->GetDamage())) + return; + } + + Remove(); + + Unit* caster = GetCaster(); + if (!caster) + return; + + uint32 spellId = sSpellMgr->GetSpellWithRank(SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_R1, GetSpellInfo()->GetRank()); + caster->CastSpell(eventInfo.GetActionTarget(), spellId, true, nullptr, aurEff); + } + + void Register() override + { + DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_warl_seed_of_corruption_dummy::CalculateBuffer, EFFECT_1, SPELL_AURA_DUMMY); + OnEffectProc += AuraEffectProcFn(spell_warl_seed_of_corruption_dummy::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// 32863, 36123, 38252, 39367, 44141, 70388 - Seed of Corruption (generic) +class spell_warl_seed_of_corruption_generic : public AuraScript +{ + PrepareAuraScript(spell_warl_seed_of_corruption_generic); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_GENERIC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + int32 amount = aurEff->GetAmount() - damageInfo->GetDamage(); + if (amount > 0) + { + const_cast(aurEff)->SetAmount(amount); + return; + } + + Remove(); + + Unit* caster = GetCaster(); + if (!caster) + return; + + caster->CastSpell(eventInfo.GetActionTarget(), SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_GENERIC, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_seed_of_corruption_generic::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); + } +}; + +// -30293 - Soul Leech +class spell_warl_soul_leech : public AuraScript +{ + PrepareAuraScript(spell_warl_soul_leech); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_WARLOCK_SOUL_LEECH_HEAL, + SPELL_WARLOCK_IMP_SOUL_LEECH_R1, + SPELL_WARLOCK_SOUL_LEECH_PET_MANA_1, + SPELL_WARLOCK_SOUL_LEECH_PET_MANA_2, + SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_1, + SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_2, + SPELL_REPLENISHMENT + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + static uint32 const casterMana[2] = { SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_1, SPELL_WARLOCK_SOUL_LEECH_CASTER_MANA_2 }; + static uint32 const petMana[2] = { SPELL_WARLOCK_SOUL_LEECH_PET_MANA_1, SPELL_WARLOCK_SOUL_LEECH_PET_MANA_2 }; + + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + Unit* caster = eventInfo.GetActor(); + int32 healAmount = CalculatePct(static_cast(damageInfo->GetDamage()), aurEff->GetAmount()); + caster->CastCustomSpell(SPELL_WARLOCK_SOUL_LEECH_HEAL, SPELLVALUE_BASE_POINT0, healAmount, caster, true, nullptr, aurEff); + + // Improved Soul Leech code below + AuraEffect const* impSoulLeech = GetTarget()->GetAuraEffectOfRankedSpell(SPELL_WARLOCK_IMP_SOUL_LEECH_R1, EFFECT_1, aurEff->GetCasterGUID()); + if (!impSoulLeech) + return; + + uint8 impSoulLeechRank = impSoulLeech->GetSpellInfo()->GetRank(); + uint32 selfSpellId = casterMana[impSoulLeechRank - 1]; + uint32 petSpellId = petMana[impSoulLeechRank - 1]; + + caster->CastSpell(nullptr, selfSpellId, true, nullptr, aurEff); + caster->CastSpell(nullptr, petSpellId, true, nullptr, aurEff); + + if (roll_chance_i(impSoulLeech->GetAmount())) + caster->CastSpell(nullptr, SPELL_REPLENISHMENT, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_soul_leech::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 37377 - Shadowflame (T4 2P fire) +class spell_warl_t4_2p_bonus_fire : public AuraScript +{ + PrepareAuraScript(spell_warl_t4_2p_bonus_fire); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_SHADOWFLAME_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_WARLOCK_SHADOWFLAME_PROC, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_t4_2p_bonus_fire::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// 39437 - Shadowflame Hellfire and RoF (T4 2P shadow) +class spell_warl_t4_2p_bonus_shadow : public AuraScript +{ + PrepareAuraScript(spell_warl_t4_2p_bonus_shadow); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARLOCK_FLAMESHADOW_PROC }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_WARLOCK_FLAMESHADOW_PROC, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warl_t4_2p_bonus_shadow::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// -30299 - Nether Protection +class spell_warl_nether_protection : public AuraScript +{ + PrepareAuraScript(spell_warl_nether_protection); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_WARLOCK_NETHER_PROTECTION_HOLY, + SPELL_WARLOCK_NETHER_PROTECTION_FIRE, + SPELL_WARLOCK_NETHER_PROTECTION_NATURE, + SPELL_WARLOCK_NETHER_PROTECTION_FROST, + SPELL_WARLOCK_NETHER_PROTECTION_SHADOW, + SPELL_WARLOCK_NETHER_PROTECTION_ARCANE + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + if (DamageInfo* damageInfo = eventInfo.GetDamageInfo()) + { + switch (GetFirstSchoolInMask(damageInfo->GetSchoolMask())) + { + case SPELL_SCHOOL_HOLY: + case SPELL_SCHOOL_FIRE: + case SPELL_SCHOOL_NATURE: + case SPELL_SCHOOL_FROST: + case SPELL_SCHOOL_SHADOW: + case SPELL_SCHOOL_ARCANE: + return true; + default: + break; + } + } + + return false; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + uint32 triggerspell = 0; + + switch (GetFirstSchoolInMask(eventInfo.GetDamageInfo()->GetSchoolMask())) + { + case SPELL_SCHOOL_HOLY: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_HOLY; + break; + case SPELL_SCHOOL_FIRE: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_FIRE; + break; + case SPELL_SCHOOL_NATURE: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_NATURE; + break; + case SPELL_SCHOOL_FROST: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_FROST; + break; + case SPELL_SCHOOL_SHADOW: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_SHADOW; + break; + case SPELL_SCHOOL_ARCANE: + triggerspell = SPELL_WARLOCK_NETHER_PROTECTION_ARCANE; + break; + default: + return; + } + + if (Unit* target = eventInfo.GetActionTarget()) + target->CastSpell(target, triggerspell, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_warl_nether_protection::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_warl_nether_protection::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_warlock_spell_scripts() { + RegisterSpellScript(spell_warl_nether_protection); RegisterSpellScript(spell_warl_eye_of_kilrogg); RegisterSpellScript(spell_warl_shadowflame); RegisterSpellScript(spell_warl_seduction); @@ -1493,4 +1985,16 @@ void AddSC_warlock_spell_scripts() RegisterSpellScript(spell_warl_shadowburn); RegisterSpellScript(spell_warl_voidwalker_pet_passive); RegisterSpellScript(spell_warl_demonic_pact_aura); + RegisterSpellScript(spell_warl_curse_of_agony); + RegisterSpellScript(spell_warl_glyph_of_corruption_nightfall); + RegisterSpellScript(spell_warl_nightfall); + RegisterSpellScript(spell_warl_decimation); + RegisterSpellScript(spell_warl_demonic_pact); + RegisterSpellScript(spell_warl_glyph_of_life_tap); + RegisterSpellScript(spell_warl_improved_drain_soul); + RegisterSpellScript(spell_warl_seed_of_corruption_dummy); + RegisterSpellScript(spell_warl_seed_of_corruption_generic); + RegisterSpellScript(spell_warl_soul_leech); + RegisterSpellScript(spell_warl_t4_2p_bonus_fire); + RegisterSpellScript(spell_warl_t4_2p_bonus_shadow); } diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index ba6e24c28..421875469 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -62,11 +62,22 @@ enum WarriorSpells SPELL_WARRIOR_WHIRLWIND_MAIN = 50622, SPELL_WARRIOR_WHIRLWIND_OFF = 44949, SPELL_WARRIOR_EXECUTE_R1 = 5308, + SPELL_WARRIOR_SECOND_WIND_HEAL_R1 = 29841, + SPELL_WARRIOR_SECOND_WIND_HEAL_R2 = 29842, + SPELL_WARRIOR_SECOND_WIND_UK = 42771, + SPELL_WARRIOR_T10_PROT_4P_ABSORB = 70845, + SPELL_WARRIOR_GLYPH_OF_BLOCKING_BUFF = 58374, + SPELL_WARRIOR_T10_MELEE_4P_BONUS = 70847, + SPELL_WARRIOR_T10_MELEE_4P_EXTRA_CHARGE = 70849, + SPELL_WARRIOR_SLAM_GCD_REDUCED = 71072, + SPELL_WARRIOR_EXECUTE_GCD_REDUCED = 71069, + SPELL_WARRIOR_WARRIORS_WRATH = 21887, }; enum WarriorSpellIcons { - WARRIOR_ICON_ID_SUDDEN_DEATH = 1989 + WARRIOR_ICON_ID_SUDDEN_DEATH = 1989, + WARRIOR_ICON_ID_SECOND_WIND = 1697 }; enum MiscSpells @@ -726,17 +737,6 @@ class spell_warr_vigilance : public AuraScript target->CastSpell(caster, SPELL_WARRIOR_VIGILANCE_REDIRECT_THREAT, true); } - void HandleAfterApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) - { - //! WORKAROUND - //! this glyph is a proc - if (Unit* caster = GetCaster()) - { - if (AuraEffect const* glyph = caster->GetAuraEffect(SPELL_WARRIOR_GLYPH_OF_VIGILANCE, EFFECT_0)) - GetTarget()->ModifyRedirectThreat(glyph->GetAmount()); - } - } - void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { Unit* target = GetTarget(); @@ -766,7 +766,6 @@ class spell_warr_vigilance : public AuraScript void Register() override { OnEffectApply += AuraEffectApplyFn(spell_warr_vigilance::HandleApply, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); - AfterEffectApply += AuraEffectApplyFn(spell_warr_vigilance::HandleAfterApply, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); OnEffectRemove += AuraEffectRemoveFn(spell_warr_vigilance::HandleRemove, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); DoCheckProc += AuraCheckProcFn(spell_warr_vigilance::CheckProc); OnEffectProc += AuraEffectProcFn(spell_warr_vigilance::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); @@ -776,6 +775,29 @@ private: Unit* _procTarget; }; +// 59665 - Vigilance (Redirect Threat) +class spell_warr_vigilance_redirect_threat : public SpellScript +{ + PrepareSpellScript(spell_warr_vigilance_redirect_threat); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARRIOR_GLYPH_OF_VIGILANCE }); + } + + void HandleGlyph(SpellEffIndex /*effIndex*/) + { + if (Unit* warrior = GetHitUnit()) + if (AuraEffect const* glyph = warrior->GetAuraEffect(SPELL_WARRIOR_GLYPH_OF_VIGILANCE, EFFECT_0)) + SetEffectValue(GetEffectValue() + glyph->GetAmount()); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_warr_vigilance_redirect_threat::HandleGlyph, EFFECT_0, SPELL_EFFECT_REDIRECT_THREAT); + } +}; + // 50725 - Vigilance class spell_warr_vigilance_trigger : public SpellScript { @@ -826,35 +848,27 @@ class spell_warr_glyph_of_sunder_armor : public AuraScript } }; -// Spell 28845 - Cheat Death - -enum CheatDeath -{ - SPELL_CHEAT_DEATH_TRIGGER = 28846 -}; - +// 28845 - Cheat Death class spell_warr_t3_prot_8p_bonus : public AuraScript { PrepareAuraScript(spell_warr_t3_prot_8p_bonus); bool CheckProc(ProcEventInfo& eventInfo) { - return eventInfo.GetActionTarget() && eventInfo.GetActionTarget()->GetHealthPct() <= 20.0f; - } + if (eventInfo.GetActionTarget()->HealthBelowPct(20)) + return true; - void HandleEffectProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) - { - PreventDefaultAction(); - if (Unit* target = eventInfo.GetActionTarget()) - { - target->CastSpell(target, SPELL_CHEAT_DEATH_TRIGGER, true); - } + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (damageInfo && damageInfo->GetDamage()) + if (GetTarget()->HealthBelowPctDamaged(20, damageInfo->GetDamage())) + return true; + + return false; } void Register() override { DoCheckProc += AuraCheckProcFn(spell_warr_t3_prot_8p_bonus::CheckProc); - OnEffectProc += AuraEffectProcFn(spell_warr_t3_prot_8p_bonus::HandleEffectProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); } }; @@ -980,6 +994,235 @@ class spell_war_sudden_death_aura : public AuraScript } }; +// Second Wind - triggers health regen when stunned or immobilized +class spell_warr_second_wind : public AuraScript +{ + PrepareAuraScript(spell_warr_second_wind); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_WARRIOR_SECOND_WIND_HEAL_R1, + SPELL_WARRIOR_SECOND_WIND_HEAL_R2, + SPELL_WARRIOR_SECOND_WIND_UK + }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + SpellInfo const* procSpell = eventInfo.GetSpellInfo(); + if (!procSpell) + return false; + + // Must be from stun or root mechanic + if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN)))) + return false; + + // Not from self + if (eventInfo.GetActionTarget() == eventInfo.GetActor()) + return false; + + return true; + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + uint32 triggeredSpellId = 0; + switch (GetId()) + { + case 29838: triggeredSpellId = SPELL_WARRIOR_SECOND_WIND_HEAL_R2; break; + case 29834: triggeredSpellId = SPELL_WARRIOR_SECOND_WIND_HEAL_R1; break; + case 42770: triggeredSpellId = SPELL_WARRIOR_SECOND_WIND_UK; break; + default: + return; + } + + GetTarget()->CastSpell(GetTarget(), triggeredSpellId, true, nullptr, aurEff); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_warr_second_wind::CheckProc); + OnEffectProc += AuraEffectProcFn(spell_warr_second_wind::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// Deep Wounds - calculates bleed damage based on weapon damage +class spell_warr_deep_wounds_aura : public AuraScript +{ + PrepareAuraScript(spell_warr_deep_wounds_aura); + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + + Unit* caster = GetTarget(); + if (!caster->IsPlayer()) + return; + + int32 basepoints; + if (eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK) + basepoints = int32((caster->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE) + caster->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE)) / 2.0f); + else + basepoints = int32((caster->GetFloatValue(UNIT_FIELD_MAXDAMAGE) + caster->GetFloatValue(UNIT_FIELD_MINDAMAGE)) / 2.0f); + + uint32 triggeredSpellId = GetSpellInfo()->Effects[EFFECT_0].TriggerSpell; + if (Unit* target = eventInfo.GetActionTarget()) + caster->CastCustomSpell(target, triggeredSpellId, &basepoints, nullptr, nullptr, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warr_deep_wounds_aura::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// Warrior T10 Melee 4P Bonus - extra effects for Sudden Death/Bloodsurge procs +class spell_warr_extra_proc : public AuraScript +{ + PrepareAuraScript(spell_warr_extra_proc); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ + SPELL_WARRIOR_T10_MELEE_4P_BONUS, + SPELL_WARRIOR_T10_MELEE_4P_EXTRA_CHARGE, + SPELL_WARRIOR_SLAM_GCD_REDUCED, + SPELL_WARRIOR_EXECUTE_GCD_REDUCED + }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + Unit* caster = GetTarget(); + uint32 triggeredSpellId = GetSpellInfo()->Effects[EFFECT_0].TriggerSpell; + + // Triggered spell IDs: 46916 = Slam!, 52437 = Sudden Death + bool isBloodsurge = (triggeredSpellId == 46916); + + // Item - Warrior T10 Melee 4P Bonus + if (AuraEffect const* t10Bonus = caster->GetAuraEffect(SPELL_WARRIOR_T10_MELEE_4P_BONUS, EFFECT_0)) + { + if (!roll_chance_i(t10Bonus->GetAmount())) + { + // Don't allow normal proc to override set one + if (caster->GetAura(isBloodsurge ? SPELL_WARRIOR_SLAM_GCD_REDUCED : SPELL_WARRIOR_EXECUTE_GCD_REDUCED)) + { + PreventDefaultAction(); + return; + } + // Just to be sure + caster->RemoveAurasDueToSpell(SPELL_WARRIOR_T10_MELEE_4P_EXTRA_CHARGE); + return; + } + + PreventDefaultAction(); + + // Fully remove all auras and reapply once more + caster->RemoveAurasDueToSpell(SPELL_WARRIOR_T10_MELEE_4P_EXTRA_CHARGE); + caster->RemoveAurasDueToSpell(SPELL_WARRIOR_SLAM_GCD_REDUCED); + caster->RemoveAurasDueToSpell(SPELL_WARRIOR_EXECUTE_GCD_REDUCED); + + caster->CastSpell(caster, SPELL_WARRIOR_T10_MELEE_4P_EXTRA_CHARGE, true, nullptr, aurEff); + caster->CastSpell(caster, triggeredSpellId, true, nullptr, aurEff); + caster->CastSpell(caster, isBloodsurge ? SPELL_WARRIOR_SLAM_GCD_REDUCED : SPELL_WARRIOR_EXECUTE_GCD_REDUCED, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warr_extra_proc::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// Sword and Board proc - remove Shield Slam cooldown +class spell_warr_sword_and_board : public AuraScript +{ + PrepareAuraScript(spell_warr_sword_and_board); + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) + { + Unit* caster = GetTarget(); + if (caster->IsPlayer()) + caster->ToPlayer()->RemoveCategoryCooldown(1209); // Shield Slam category + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warr_sword_and_board::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + +// Glyph of Blocking - triggers block value buff +class spell_warr_glyph_of_blocking : public AuraScript +{ + PrepareAuraScript(spell_warr_glyph_of_blocking); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARRIOR_GLYPH_OF_BLOCKING_BUFF }); + } + + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + GetTarget()->CastSpell(GetTarget(), SPELL_WARRIOR_GLYPH_OF_BLOCKING_BUFF, true, nullptr, aurEff); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_warr_glyph_of_blocking::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); + } +}; + +// Item - Warrior T10 Protection 4P Bonus +class spell_warr_item_t10_prot_4p_bonus : public AuraScript +{ + PrepareAuraScript(spell_warr_item_t10_prot_4p_bonus); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARRIOR_T10_PROT_4P_ABSORB }); + } + + void HandleProc(ProcEventInfo& /*eventInfo*/) + { + PreventDefaultAction(); + + Unit* caster = GetTarget(); + int32 basepoints = CalculatePct(static_cast(caster->GetMaxHealth()), GetSpellInfo()->Effects[EFFECT_1].CalcValue()); + caster->CastCustomSpell(caster, SPELL_WARRIOR_T10_PROT_4P_ABSORB, &basepoints, nullptr, nullptr, true, nullptr, GetEffect(EFFECT_0)); + } + + void Register() override + { + OnProc += AuraProcFn(spell_warr_item_t10_prot_4p_bonus::HandleProc); + } +}; + +// 21977 - Warrior's Wrath (T3 8P Bonus) +class spell_warr_warriors_wrath : public SpellScript +{ + PrepareSpellScript(spell_warr_warriors_wrath); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WARRIOR_WARRIORS_WRATH }); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetCaster(), SPELL_WARRIOR_WARRIORS_WRATH, true); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_warr_warriors_wrath::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } +}; + void AddSC_warrior_spell_scripts() { RegisterSpellScript(spell_warr_mocking_blow); @@ -1004,8 +1247,16 @@ void AddSC_warrior_spell_scripts() RegisterSpellScript(spell_warr_slam); RegisterSpellScript(spell_warr_sweeping_strikes); RegisterSpellScript(spell_warr_vigilance); + RegisterSpellScript(spell_warr_vigilance_redirect_threat); RegisterSpellScript(spell_warr_vigilance_trigger); + RegisterSpellScript(spell_warr_warriors_wrath); RegisterSpellScript(spell_warr_t3_prot_8p_bonus); RegisterSpellScript(spell_warr_heroic_strike); RegisterSpellScript(spell_war_sudden_death_aura); + RegisterSpellScript(spell_warr_second_wind); + RegisterSpellScript(spell_warr_deep_wounds_aura); + RegisterSpellScript(spell_warr_extra_proc); + RegisterSpellScript(spell_warr_sword_and_board); + RegisterSpellScript(spell_warr_glyph_of_blocking); + RegisterSpellScript(spell_warr_item_t10_prot_4p_bonus); } diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 7a07c16e6..31b1f84f7 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -390,7 +390,7 @@ uint32 constexpr QuestDifficultyColors[MAX_QUEST_DIFFICULTY] = // EnumUtils: DESCRIBE THIS enum SpellAttr0 : uint32 { - SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE = 0x00000001, // TITLE Unknown attribute 0@Attr0 + SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE = 0x00000001, // TITLE Proc Failure Burns Charge SPELL_ATTR0_USES_RANGED_SLOT = 0x00000002, // TITLE Treat as ranged attack DESCRIPTION Use ammo, ranged attack range modifiers, ranged haste, etc. SPELL_ATTR0_ON_NEXT_SWING_NO_DAMAGE = 0x00000004, // TITLE On next melee (type 1) DESCRIPTION Both "on next swing" attributes have identical handling in server & client SPELL_ATTR0_DO_NOT_LOG_IMMUNE_MISSES = 0x00000008, // TITLE Replenishment (client only) @@ -487,7 +487,7 @@ enum SpellAttr2 : uint32 SPELL_ATTR2_INITIATE_COMBAT_POST_CAST = 0x00100000, // TITLE (Enables Auto-Attack) SPELL_ATTR2_FAIL_ON_ALL_TARGETS_IMMUNE = 0x00200000, // TITLE Damage reduction ability DESCRIPTION Causes BG flags to be dropped if combined with ATTR1_DISPEL_AURAS_ON_IMMUNITY SPELL_ATTR2_NO_INITIAL_THREAD = 0x00400000, // TITLE Unknown attribute 22@Attr2 DESCRIPTION Ambush, Backstab, Cheap Shot, Death Grip, Garrote, Judgements, Mutilate, Pounce, Ravage, Shiv, Shred - SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE = 0x00800000, // TITLE Arcane Concentration + SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE = 0x00800000, // TITLE Proc Cooldown On Failure SPELL_ATTR2_ITEM_CAST_WITH_OWNER_SKILL = 0x01000000, // TITLE Unknown attribute 24@Attr2 SPELL_ATTR2_DONT_BLOCK_MANA_REGEN = 0x02000000, // TITLE Unknown attribute 25@Attr2 SPELL_ATTR2_NO_SCHOOL_IMMUNITIES = 0x04000000, // TITLE Pierce aura application immunities DESCRIPTION Allow aura to be applied despite target being immune to new aura applications diff --git a/src/server/shared/enuminfo_SharedDefines.cpp b/src/server/shared/enuminfo_SharedDefines.cpp index 53e4ec80c..d8e6f7634 100644 --- a/src/server/shared/enuminfo_SharedDefines.cpp +++ b/src/server/shared/enuminfo_SharedDefines.cpp @@ -158,7 +158,7 @@ AC_API_EXPORT EnumText EnumUtils::ToString(SpellAttr0 value) { switch (value) { - case SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE: return { "SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE", "Unknown attribute 0@Attr0", "" }; + case SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE: return { "SPELL_ATTR0_PROC_FAILURE_BURNS_CHARGE", "Proc Failure Burns Charge", "" }; case SPELL_ATTR0_USES_RANGED_SLOT: return { "SPELL_ATTR0_USES_RANGED_SLOT", "Treat as ranged attack", "Use ammo, ranged attack range modifiers, ranged haste, etc." }; case SPELL_ATTR0_ON_NEXT_SWING_NO_DAMAGE: return { "SPELL_ATTR0_ON_NEXT_SWING_NO_DAMAGE", "On next melee (type 1)", "Both \042on next swing\042 attributes have identical handling in server & client" }; case SPELL_ATTR0_DO_NOT_LOG_IMMUNE_MISSES: return { "SPELL_ATTR0_DO_NOT_LOG_IMMUNE_MISSES", "Replenishment (client only)", "" }; @@ -439,7 +439,7 @@ AC_API_EXPORT EnumText EnumUtils::ToString(SpellAttr2 value) case SPELL_ATTR2_INITIATE_COMBAT_POST_CAST: return { "SPELL_ATTR2_INITIATE_COMBAT_POST_CAST", "(Enables Auto-Attack)", "" }; case SPELL_ATTR2_FAIL_ON_ALL_TARGETS_IMMUNE: return { "SPELL_ATTR2_FAIL_ON_ALL_TARGETS_IMMUNE", "Damage reduction ability", "Causes BG flags to be dropped if combined with ATTR1_DISPEL_AURAS_ON_IMMUNITY" }; case SPELL_ATTR2_NO_INITIAL_THREAD: return { "SPELL_ATTR2_NO_INITIAL_THREAD", "Unknown attribute 22@Attr2", "Ambush, Backstab, Cheap Shot, Death Grip, Garrote, Judgements, Mutilate, Pounce, Ravage, Shiv, Shred" }; - case SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE: return { "SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE", "Arcane Concentration", "" }; + case SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE: return { "SPELL_ATTR2_PROC_COOLDOWN_ON_FAILURE", "Proc Cooldown On Failure", "" }; case SPELL_ATTR2_ITEM_CAST_WITH_OWNER_SKILL: return { "SPELL_ATTR2_ITEM_CAST_WITH_OWNER_SKILL", "Unknown attribute 24@Attr2", "" }; case SPELL_ATTR2_DONT_BLOCK_MANA_REGEN: return { "SPELL_ATTR2_DONT_BLOCK_MANA_REGEN", "Unknown attribute 25@Attr2", "" }; case SPELL_ATTR2_NO_SCHOOL_IMMUNITIES: return { "SPELL_ATTR2_NO_SCHOOL_IMMUNITIES", "Pierce aura application immunities", "Allow aura to be applied despite target being immune to new aura applications" }; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index bf5386d5b..6a143748c 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -15,7 +15,7 @@ CollectSourceFiles( ) include_directories( - "mocks" + "${CMAKE_CURRENT_SOURCE_DIR}/mocks" ) add_executable( diff --git a/src/test/mocks/AuraScriptTestFramework.h b/src/test/mocks/AuraScriptTestFramework.h new file mode 100644 index 000000000..9b620cdf6 --- /dev/null +++ b/src/test/mocks/AuraScriptTestFramework.h @@ -0,0 +1,484 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H +#define AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H + +#include "AuraStub.h" +#include "DamageHealInfoStub.h" +#include "ProcEventInfoHelper.h" +#include "SpellInfoTestHelper.h" +#include "UnitStub.h" +#include "SpellMgr.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include +#include + +/** + * @brief Simulated proc result for testing + */ +struct ProcTestResult +{ + bool shouldProc = false; + uint8_t effectMask = 0; + float procChance = 100.0f; + std::vector spellsCast; + bool chargeConsumed = false; + bool cooldownSet = false; +}; + +/** + * @brief Context for a proc test scenario + */ +class ProcTestContext +{ +public: + ProcTestContext() = default; + + // Actor (the one doing something that triggers the proc) + UnitStub& GetActor() { return _actor; } + UnitStub const& GetActor() const { return _actor; } + + // Target (the one being affected) + UnitStub& GetTarget() { return _target; } + UnitStub const& GetTarget() const { return _target; } + + // The aura that might proc + AuraStub& GetAura() { return _aura; } + AuraStub const& GetAura() const { return _aura; } + + // Damage info for damage-based procs + DamageInfoStub& GetDamageInfo() { return _damageInfo; } + DamageInfoStub const& GetDamageInfo() const { return _damageInfo; } + + // Heal info for heal-based procs + HealInfoStub& GetHealInfo() { return _healInfo; } + HealInfoStub const& GetHealInfo() const { return _healInfo; } + + // Setup methods + ProcTestContext& WithAuraId(uint32_t auraId) + { + _aura.SetId(auraId); + return *this; + } + + ProcTestContext& WithAuraSpellFamily(uint32_t familyName) + { + _aura.SetSpellFamilyName(familyName); + return *this; + } + + ProcTestContext& WithAuraCharges(uint8_t charges) + { + _aura.SetCharges(charges); + _aura.SetUsingCharges(charges > 0); + return *this; + } + + ProcTestContext& WithActorAsPlayer(bool isPlayer = true) + { + _actor.SetIsPlayer(isPlayer); + return *this; + } + + ProcTestContext& WithDamage(uint32_t damage, uint32_t schoolMask = 1) + { + _damageInfo.SetDamage(damage); + _damageInfo.SetOriginalDamage(damage); + _damageInfo.SetSchoolMask(schoolMask); + return *this; + } + + ProcTestContext& WithHeal(uint32_t heal, uint32_t effectiveHeal = 0) + { + _healInfo.SetHeal(heal); + _healInfo.SetEffectiveHeal(effectiveHeal > 0 ? effectiveHeal : heal); + return *this; + } + + ProcTestContext& WithCriticalHit() + { + _damageInfo.SetHitMask(PROC_HIT_CRITICAL); + _healInfo.SetHitMask(PROC_HIT_CRITICAL); + return *this; + } + + ProcTestContext& WithNormalHit() + { + _damageInfo.SetHitMask(PROC_HIT_NORMAL); + _healInfo.SetHitMask(PROC_HIT_NORMAL); + return *this; + } + +private: + UnitStub _actor; + UnitStub _target; + AuraStub _aura; + DamageInfoStub _damageInfo; + HealInfoStub _healInfo; +}; + +/** + * @brief Base fixture for AuraScript proc testing + * + * This provides infrastructure for testing proc behavior at the unit level + * without requiring full game objects. + */ +class AuraScriptProcTestFixture : public ::testing::Test +{ +protected: + void SetUp() override + { + _context = std::make_unique(); + _spellInfos.clear(); + } + + void TearDown() override + { + for (auto* spellInfo : _spellInfos) + { + delete spellInfo; + } + _spellInfos.clear(); + } + + // Access the test context + ProcTestContext& Context() { return *_context; } + + // Create and track a test SpellInfo + SpellInfo* CreateSpellInfo(uint32_t id, uint32_t familyName = 0, + uint32_t familyFlags0 = 0, uint32_t familyFlags1 = 0, + uint32_t familyFlags2 = 0) + { + auto* spellInfo = SpellInfoBuilder() + .WithId(id) + .WithSpellFamilyName(familyName) + .WithSpellFamilyFlags(familyFlags0, familyFlags1, familyFlags2) + .Build(); + _spellInfos.push_back(spellInfo); + return spellInfo; + } + + // Create a test SpellProcEntry + SpellProcEntry CreateProcEntry() + { + return SpellProcEntryBuilder().Build(); + } + + // Create a test ProcEventInfo + ProcEventInfo CreateEventInfo(uint32_t typeMask, uint32_t hitMask, + uint32_t spellTypeMask = PROC_SPELL_TYPE_MASK_ALL, + uint32_t spellPhaseMask = PROC_SPELL_PHASE_HIT) + { + return ProcEventInfoBuilder() + .WithTypeMask(typeMask) + .WithHitMask(hitMask) + .WithSpellTypeMask(spellTypeMask) + .WithSpellPhaseMask(spellPhaseMask) + .Build(); + } + + // Test if a proc entry would trigger with given event info + bool TestCanProc(SpellProcEntry const& procEntry, uint32_t typeMask, + uint32_t hitMask, SpellInfo const* triggerSpell = nullptr) + { + DamageInfo* damageInfoPtr = nullptr; + HealInfo* healInfoPtr = nullptr; + + // Create real DamageInfo/HealInfo if we have a trigger spell + // Note: This requires the actual game classes, which may need adjustment + // For now, we use the stub approach + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(typeMask) + .WithHitMask(hitMask) + .WithSpellTypeMask(PROC_SPELL_TYPE_MASK_ALL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + return sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo); + } + + // Check if spell family matches + bool TestSpellFamilyMatch(uint32_t procFamilyName, flag96 const& procFamilyMask, + SpellInfo const* triggerSpell) + { + if (procFamilyName && triggerSpell) + { + if (procFamilyName != triggerSpell->SpellFamilyName) + return false; + + if (procFamilyMask) + { + flag96 triggerMask; + triggerMask[0] = triggerSpell->SpellFamilyFlags[0]; + triggerMask[1] = triggerSpell->SpellFamilyFlags[1]; + triggerMask[2] = triggerSpell->SpellFamilyFlags[2]; + + if (!(triggerMask & procFamilyMask)) + return false; + } + } + return true; + } + +private: + std::unique_ptr _context; + std::vector _spellInfos; +}; + +/** + * @brief Helper class for testing specific proc scenarios + * + * Uses shared_ptr for resource management to allow safe copying + * in fluent builder pattern usage. + */ +class ProcScenarioBuilder +{ +public: + ProcScenarioBuilder() + { + // Create a default SpellInfo for spell-type procs using shared_ptr + _defaultSpellInfo = std::shared_ptr( + SpellInfoBuilder() + .WithId(99999) + .WithSpellFamilyName(0) + .Build() + ); + } + + ~ProcScenarioBuilder() = default; + + // Configure the triggering action + ProcScenarioBuilder& OnMeleeAutoAttack() + { + _typeMask = PROC_FLAG_DONE_MELEE_AUTO_ATTACK; + _needsSpellInfo = false; + return *this; + } + + ProcScenarioBuilder& OnTakenMeleeAutoAttack() + { + _typeMask = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; + _needsSpellInfo = false; + return *this; + } + + ProcScenarioBuilder& OnSpellDamage() + { + _typeMask = PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; + _spellTypeMask = PROC_SPELL_TYPE_DAMAGE; + _needsSpellInfo = true; + _usesDamageInfo = true; + return *this; + } + + ProcScenarioBuilder& OnTakenSpellDamage() + { + _typeMask = PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG; + _spellTypeMask = PROC_SPELL_TYPE_DAMAGE; + _needsSpellInfo = true; + _usesDamageInfo = true; + return *this; + } + + ProcScenarioBuilder& OnHeal() + { + _typeMask = PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; + _spellTypeMask = PROC_SPELL_TYPE_HEAL; + _needsSpellInfo = true; + _usesHealInfo = true; + return *this; + } + + ProcScenarioBuilder& OnTakenHeal() + { + _typeMask = PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS; + _spellTypeMask = PROC_SPELL_TYPE_HEAL; + _needsSpellInfo = true; + _usesHealInfo = true; + return *this; + } + + ProcScenarioBuilder& OnPeriodicDamage() + { + _typeMask = PROC_FLAG_DONE_PERIODIC; + _spellTypeMask = PROC_SPELL_TYPE_DAMAGE; + _needsSpellInfo = true; + _usesDamageInfo = true; + return *this; + } + + ProcScenarioBuilder& OnPeriodicHeal() + { + _typeMask = PROC_FLAG_DONE_PERIODIC; + _spellTypeMask = PROC_SPELL_TYPE_HEAL; + _needsSpellInfo = true; + _usesHealInfo = true; + return *this; + } + + ProcScenarioBuilder& OnKill() + { + _typeMask = PROC_FLAG_KILL; + _needsSpellInfo = false; + return *this; + } + + ProcScenarioBuilder& OnDeath() + { + _typeMask = PROC_FLAG_DEATH; + _needsSpellInfo = false; + return *this; + } + + // Configure hit result + ProcScenarioBuilder& WithCrit() + { + _hitMask = PROC_HIT_CRITICAL; + return *this; + } + + ProcScenarioBuilder& WithNormalHit() + { + _hitMask = PROC_HIT_NORMAL; + return *this; + } + + ProcScenarioBuilder& WithMiss() + { + _hitMask = PROC_HIT_MISS; + return *this; + } + + ProcScenarioBuilder& WithDodge() + { + _hitMask = PROC_HIT_DODGE; + return *this; + } + + ProcScenarioBuilder& WithParry() + { + _hitMask = PROC_HIT_PARRY; + return *this; + } + + ProcScenarioBuilder& WithBlock() + { + _hitMask = PROC_HIT_BLOCK; + return *this; + } + + ProcScenarioBuilder& WithFullBlock() + { + _hitMask = PROC_HIT_FULL_BLOCK; + return *this; + } + + ProcScenarioBuilder& WithAbsorb() + { + _hitMask = PROC_HIT_ABSORB; + return *this; + } + + // Note: PROC_HIT_ABSORB covers both partial and full absorb + // There is no separate PROC_HIT_FULL_ABSORB flag in AzerothCore + + // Configure spell phase + ProcScenarioBuilder& OnCast() + { + _spellPhaseMask = PROC_SPELL_PHASE_CAST; + return *this; + } + + ProcScenarioBuilder& OnHit() + { + _spellPhaseMask = PROC_SPELL_PHASE_HIT; + return *this; + } + + ProcScenarioBuilder& OnFinish() + { + _spellPhaseMask = PROC_SPELL_PHASE_FINISH; + return *this; + } + + // Build the scenario into a ProcEventInfo + ProcEventInfo Build() + { + auto builder = ProcEventInfoBuilder() + .WithTypeMask(_typeMask) + .WithHitMask(_hitMask) + .WithSpellTypeMask(_spellTypeMask) + .WithSpellPhaseMask(_spellPhaseMask); + + // Create DamageInfo or HealInfo with SpellInfo for spell-type procs + if (_needsSpellInfo) + { + if (_usesDamageInfo) + { + // Create new DamageInfo if needed + if (!_damageInfo) + _damageInfo = std::make_shared(nullptr, nullptr, 100, _defaultSpellInfo.get(), SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + builder.WithDamageInfo(_damageInfo.get()); + } + else if (_usesHealInfo) + { + // Create new HealInfo if needed + if (!_healInfo) + _healInfo = std::make_shared(nullptr, nullptr, 100, _defaultSpellInfo.get(), SPELL_SCHOOL_MASK_HOLY); + builder.WithHealInfo(_healInfo.get()); + } + } + + return builder.Build(); + } + + // Get individual values + [[nodiscard]] uint32_t GetTypeMask() const { return _typeMask; } + [[nodiscard]] uint32_t GetHitMask() const { return _hitMask; } + [[nodiscard]] uint32_t GetSpellTypeMask() const { return _spellTypeMask; } + [[nodiscard]] uint32_t GetSpellPhaseMask() const { return _spellPhaseMask; } + +private: + uint32_t _typeMask = 0; + uint32_t _hitMask = PROC_HIT_NORMAL; + uint32_t _spellTypeMask = PROC_SPELL_TYPE_MASK_ALL; + uint32_t _spellPhaseMask = PROC_SPELL_PHASE_HIT; + bool _needsSpellInfo = false; + bool _usesDamageInfo = false; + bool _usesHealInfo = false; + std::shared_ptr _defaultSpellInfo; + std::shared_ptr _damageInfo; + std::shared_ptr _healInfo; +}; + +// Convenience macros for proc testing +#define EXPECT_PROC_TRIGGERS(procEntry, scenario) \ + do { \ + auto _eventInfo = (scenario).Build(); \ + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, _eventInfo)); \ + } while(0) + +#define EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, scenario) \ + do { \ + auto _eventInfo = (scenario).Build(); \ + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, _eventInfo)); \ + } while(0) + +#endif //AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H diff --git a/src/test/mocks/AuraStub.h b/src/test/mocks/AuraStub.h new file mode 100644 index 000000000..ee1e3b592 --- /dev/null +++ b/src/test/mocks/AuraStub.h @@ -0,0 +1,367 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_AURA_STUB_H +#define AZEROTHCORE_AURA_STUB_H + +#include "gmock/gmock.h" +#include +#include +#include +#include + +class SpellInfo; +class UnitStub; + +/** + * @brief Lightweight stub for AuraEffect proc-related functionality + */ +class AuraEffectStub +{ +public: + AuraEffectStub(uint8_t effIndex = 0, int32_t amount = 0, uint32_t auraType = 0) + : _effIndex(effIndex), _amount(amount), _auraType(auraType) {} + + virtual ~AuraEffectStub() = default; + + [[nodiscard]] uint8_t GetEffIndex() const { return _effIndex; } + [[nodiscard]] int32_t GetAmount() const { return _amount; } + [[nodiscard]] uint32_t GetAuraType() const { return _auraType; } + [[nodiscard]] int32_t GetBaseAmount() const { return _baseAmount; } + [[nodiscard]] float GetCritChance() const { return _critChance; } + + void SetEffIndex(uint8_t effIndex) { _effIndex = effIndex; } + void SetAmount(int32_t amount) { _amount = amount; } + void SetAuraType(uint32_t auraType) { _auraType = auraType; } + void SetBaseAmount(int32_t baseAmount) { _baseAmount = baseAmount; } + void SetCritChance(float critChance) { _critChance = critChance; } + + // Periodic tracking + [[nodiscard]] bool IsPeriodic() const { return _isPeriodic; } + [[nodiscard]] int32_t GetTotalTicks() const { return _totalTicks; } + [[nodiscard]] uint32_t GetTickNumber() const { return _tickNumber; } + + void SetPeriodic(bool isPeriodic) { _isPeriodic = isPeriodic; } + void SetTotalTicks(int32_t totalTicks) { _totalTicks = totalTicks; } + void SetTickNumber(uint32_t tickNumber) { _tickNumber = tickNumber; } + +private: + uint8_t _effIndex = 0; + int32_t _amount = 0; + int32_t _baseAmount = 0; + uint32_t _auraType = 0; + float _critChance = 0.0f; + bool _isPeriodic = false; + int32_t _totalTicks = 0; + uint32_t _tickNumber = 0; +}; + +/** + * @brief Lightweight stub for AuraApplication functionality + */ +class AuraApplicationStub +{ +public: + AuraApplicationStub() = default; + virtual ~AuraApplicationStub() = default; + + [[nodiscard]] uint8_t GetEffectMask() const { return _effectMask; } + [[nodiscard]] bool HasEffect(uint8_t effIndex) const + { + return (_effectMask & (1 << effIndex)) != 0; + } + [[nodiscard]] bool IsPositive() const { return _isPositive; } + [[nodiscard]] uint8_t GetSlot() const { return _slot; } + + void SetEffectMask(uint8_t mask) { _effectMask = mask; } + void SetEffect(uint8_t effIndex, bool enabled) + { + if (enabled) + _effectMask |= (1 << effIndex); + else + _effectMask &= ~(1 << effIndex); + } + void SetPositive(bool isPositive) { _isPositive = isPositive; } + void SetSlot(uint8_t slot) { _slot = slot; } + +private: + uint8_t _effectMask = 0x07; // All 3 effects by default + bool _isPositive = true; + uint8_t _slot = 0; +}; + +/** + * @brief Lightweight stub for Aura proc-related functionality + */ +class AuraStub +{ +public: + AuraStub(uint32_t id = 0, uint32_t spellFamilyName = 0) + : _id(id), _spellFamilyName(spellFamilyName) + { + // Create 3 effect slots by default + for (int i = 0; i < 3; ++i) + { + _effects[i] = std::make_unique(static_cast(i)); + } + } + + virtual ~AuraStub() = default; + + // Basic identification + [[nodiscard]] uint32_t GetId() const { return _id; } + [[nodiscard]] uint32_t GetSpellFamilyName() const { return _spellFamilyName; } + + void SetId(uint32_t id) { _id = id; } + void SetSpellFamilyName(uint32_t familyName) { _spellFamilyName = familyName; } + + // Effect access + [[nodiscard]] AuraEffectStub* GetEffect(uint8_t effIndex) const + { + return (effIndex < 3) ? _effects[effIndex].get() : nullptr; + } + + [[nodiscard]] bool HasEffect(uint8_t effIndex) const + { + return effIndex < 3 && _effects[effIndex] != nullptr; + } + + [[nodiscard]] uint8_t GetEffectMask() const + { + uint8_t mask = 0; + for (uint8_t i = 0; i < 3; ++i) + { + if (_effects[i]) + mask |= (1 << i); + } + return mask; + } + + // Charges management + [[nodiscard]] uint8_t GetCharges() const { return _charges; } + [[nodiscard]] bool IsUsingCharges() const { return _isUsingCharges; } + + void SetCharges(uint8_t charges) { _charges = charges; } + void SetUsingCharges(bool usingCharges) { _isUsingCharges = usingCharges; } + + virtual bool DropCharge() + { + if (_charges > 0) + { + --_charges; + _chargeDropped = true; + return true; + } + return false; + } + + [[nodiscard]] bool WasChargeDropped() const { return _chargeDropped; } + void ResetChargeDropped() { _chargeDropped = false; } + + // Duration + [[nodiscard]] int32_t GetDuration() const { return _duration; } + [[nodiscard]] int32_t GetMaxDuration() const { return _maxDuration; } + [[nodiscard]] bool IsPermanent() const { return _maxDuration == -1; } + + void SetDuration(int32_t duration) { _duration = duration; } + void SetMaxDuration(int32_t maxDuration) { _maxDuration = maxDuration; } + + // Cooldown tracking + using TimePoint = std::chrono::steady_clock::time_point; + + [[nodiscard]] bool IsProcOnCooldown(TimePoint now) const + { + return now < _procCooldown; + } + + void AddProcCooldown(TimePoint cooldownEnd) + { + _procCooldown = cooldownEnd; + } + + void ResetProcCooldown() + { + _procCooldown = TimePoint::min(); + } + + // Stack amount + [[nodiscard]] uint8_t GetStackAmount() const { return _stackAmount; } + void SetStackAmount(uint8_t amount) { _stackAmount = amount; } + + /** + * @brief Modify stack amount (for PROC_ATTR_USE_STACKS_FOR_CHARGES) + * Mimics Aura::ModStackAmount() - removes aura if stacks reach 0 + */ + virtual bool ModStackAmount(int32_t amount, bool /* resetPeriodicTimer */ = true) + { + int32_t newAmount = static_cast(_stackAmount) + amount; + if (newAmount <= 0) + { + _stackAmount = 0; + Remove(); + return true; // Aura removed + } + _stackAmount = static_cast(newAmount); + return false; + } + + // Aura flags + [[nodiscard]] bool IsPassive() const { return _isPassive; } + [[nodiscard]] bool IsRemoved() const { return _isRemoved; } + + void SetPassive(bool isPassive) { _isPassive = isPassive; } + void SetRemoved(bool isRemoved) { _isRemoved = isRemoved; } + + /** + * @brief Mark aura as removed (for charge exhaustion) + * Mimics Aura::Remove() + */ + virtual void Remove() + { + _isRemoved = true; + } + + // Application management + AuraApplicationStub& GetOrCreateApplication() + { + if (!_application) + _application = std::make_unique(); + return *_application; + } + + [[nodiscard]] AuraApplicationStub* GetApplication() const + { + return _application.get(); + } + +private: + uint32_t _id = 0; + uint32_t _spellFamilyName = 0; + + std::unique_ptr _effects[3]; + std::unique_ptr _application; + + uint8_t _charges = 0; + bool _isUsingCharges = false; + bool _chargeDropped = false; + + int32_t _duration = -1; + int32_t _maxDuration = -1; + + TimePoint _procCooldown = TimePoint::min(); + + uint8_t _stackAmount = 1; + bool _isPassive = false; + bool _isRemoved = false; +}; + +/** + * @brief GMock-enabled Aura stub for verification + */ +class MockAuraStub : public AuraStub +{ +public: + MockAuraStub(uint32_t id = 0, uint32_t spellFamilyName = 0) + : AuraStub(id, spellFamilyName) {} + + MOCK_METHOD(bool, DropCharge, (), (override)); + MOCK_METHOD(bool, ModStackAmount, (int32_t amount, bool resetPeriodicTimer), (override)); + MOCK_METHOD(void, Remove, (), (override)); +}; + +/** + * @brief Builder for creating AuraStub instances with fluent API + */ +class AuraStubBuilder +{ +public: + AuraStubBuilder() : _stub(std::make_unique()) {} + + AuraStubBuilder& WithId(uint32_t id) + { + _stub->SetId(id); + return *this; + } + + AuraStubBuilder& WithSpellFamilyName(uint32_t familyName) + { + _stub->SetSpellFamilyName(familyName); + return *this; + } + + AuraStubBuilder& WithCharges(uint8_t charges) + { + _stub->SetCharges(charges); + _stub->SetUsingCharges(charges > 0); + return *this; + } + + AuraStubBuilder& WithDuration(int32_t duration) + { + _stub->SetDuration(duration); + _stub->SetMaxDuration(duration); + return *this; + } + + AuraStubBuilder& WithStackAmount(uint8_t amount) + { + _stub->SetStackAmount(amount); + return *this; + } + + AuraStubBuilder& WithPassive(bool isPassive) + { + _stub->SetPassive(isPassive); + return *this; + } + + AuraStubBuilder& WithEffect(uint8_t effIndex, int32_t amount, uint32_t auraType = 0) + { + if (AuraEffectStub* eff = _stub->GetEffect(effIndex)) + { + eff->SetAmount(amount); + eff->SetAuraType(auraType); + } + return *this; + } + + AuraStubBuilder& WithPeriodicEffect(uint8_t effIndex, int32_t amount, int32_t totalTicks) + { + if (AuraEffectStub* eff = _stub->GetEffect(effIndex)) + { + eff->SetAmount(amount); + eff->SetPeriodic(true); + eff->SetTotalTicks(totalTicks); + } + return *this; + } + + std::unique_ptr Build() + { + return std::move(_stub); + } + + AuraStub* BuildRaw() + { + return _stub.release(); + } + +private: + std::unique_ptr _stub; +}; + +#endif //AZEROTHCORE_AURA_STUB_H diff --git a/src/test/mocks/DamageHealInfoStub.h b/src/test/mocks/DamageHealInfoStub.h new file mode 100644 index 000000000..e16090acf --- /dev/null +++ b/src/test/mocks/DamageHealInfoStub.h @@ -0,0 +1,259 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_DAMAGE_HEAL_INFO_STUB_H +#define AZEROTHCORE_DAMAGE_HEAL_INFO_STUB_H + +#include + +class SpellInfo; +class UnitStub; + +/** + * @brief Lightweight stub for DamageInfo + * + * Mirrors the key fields of DamageInfo for proc testing without + * requiring actual Unit objects. + */ +class DamageInfoStub +{ +public: + DamageInfoStub() = default; + + DamageInfoStub(uint32_t damage, uint32_t originalDamage, uint32_t schoolMask, + uint8_t attackType, SpellInfo const* spellInfo = nullptr) + : _damage(damage) + , _originalDamage(originalDamage) + , _schoolMask(schoolMask) + , _attackType(attackType) + , _spellInfo(spellInfo) + {} + + virtual ~DamageInfoStub() = default; + + // Damage values + [[nodiscard]] uint32_t GetDamage() const { return _damage; } + [[nodiscard]] uint32_t GetOriginalDamage() const { return _originalDamage; } + [[nodiscard]] uint32_t GetAbsorb() const { return _absorb; } + [[nodiscard]] uint32_t GetResist() const { return _resist; } + [[nodiscard]] uint32_t GetBlock() const { return _block; } + + void SetDamage(uint32_t damage) { _damage = damage; } + void SetOriginalDamage(uint32_t damage) { _originalDamage = damage; } + void SetAbsorb(uint32_t absorb) { _absorb = absorb; } + void SetResist(uint32_t resist) { _resist = resist; } + void SetBlock(uint32_t block) { _block = block; } + + // School and attack type + [[nodiscard]] uint32_t GetSchoolMask() const { return _schoolMask; } + [[nodiscard]] uint8_t GetAttackType() const { return _attackType; } + + void SetSchoolMask(uint32_t schoolMask) { _schoolMask = schoolMask; } + void SetAttackType(uint8_t attackType) { _attackType = attackType; } + + // Spell info + [[nodiscard]] SpellInfo const* GetSpellInfo() const { return _spellInfo; } + void SetSpellInfo(SpellInfo const* spellInfo) { _spellInfo = spellInfo; } + + // Hit result flags + [[nodiscard]] uint32_t GetHitMask() const { return _hitMask; } + void SetHitMask(uint32_t hitMask) { _hitMask = hitMask; } + +private: + uint32_t _damage = 0; + uint32_t _originalDamage = 0; + uint32_t _absorb = 0; + uint32_t _resist = 0; + uint32_t _block = 0; + uint32_t _schoolMask = 1; // SPELL_SCHOOL_MASK_NORMAL + uint8_t _attackType = 0; // BASE_ATTACK + uint32_t _hitMask = 0; + SpellInfo const* _spellInfo = nullptr; +}; + +/** + * @brief Lightweight stub for HealInfo + * + * Mirrors the key fields of HealInfo for proc testing. + */ +class HealInfoStub +{ +public: + HealInfoStub() = default; + + HealInfoStub(uint32_t heal, uint32_t effectiveHeal, uint32_t absorb, + SpellInfo const* spellInfo = nullptr) + : _heal(heal) + , _effectiveHeal(effectiveHeal) + , _absorb(absorb) + , _spellInfo(spellInfo) + {} + + virtual ~HealInfoStub() = default; + + // Heal values + [[nodiscard]] uint32_t GetHeal() const { return _heal; } + [[nodiscard]] uint32_t GetEffectiveHeal() const { return _effectiveHeal; } + [[nodiscard]] uint32_t GetAbsorb() const { return _absorb; } + [[nodiscard]] uint32_t GetOverheal() const { return _heal > _effectiveHeal ? _heal - _effectiveHeal : 0; } + + void SetHeal(uint32_t heal) { _heal = heal; } + void SetEffectiveHeal(uint32_t effectiveHeal) { _effectiveHeal = effectiveHeal; } + void SetAbsorb(uint32_t absorb) { _absorb = absorb; } + + // Spell info + [[nodiscard]] SpellInfo const* GetSpellInfo() const { return _spellInfo; } + void SetSpellInfo(SpellInfo const* spellInfo) { _spellInfo = spellInfo; } + + // Hit result flags + [[nodiscard]] uint32_t GetHitMask() const { return _hitMask; } + void SetHitMask(uint32_t hitMask) { _hitMask = hitMask; } + +private: + uint32_t _heal = 0; + uint32_t _effectiveHeal = 0; + uint32_t _absorb = 0; + uint32_t _hitMask = 0; + SpellInfo const* _spellInfo = nullptr; +}; + +/** + * @brief Builder for creating DamageInfoStub instances with fluent API + */ +class DamageInfoStubBuilder +{ +public: + DamageInfoStubBuilder() = default; + + DamageInfoStubBuilder& WithDamage(uint32_t damage) + { + _stub.SetDamage(damage); + _stub.SetOriginalDamage(damage); + return *this; + } + + DamageInfoStubBuilder& WithOriginalDamage(uint32_t damage) + { + _stub.SetOriginalDamage(damage); + return *this; + } + + DamageInfoStubBuilder& WithSchoolMask(uint32_t schoolMask) + { + _stub.SetSchoolMask(schoolMask); + return *this; + } + + DamageInfoStubBuilder& WithAttackType(uint8_t attackType) + { + _stub.SetAttackType(attackType); + return *this; + } + + DamageInfoStubBuilder& WithSpellInfo(SpellInfo const* spellInfo) + { + _stub.SetSpellInfo(spellInfo); + return *this; + } + + DamageInfoStubBuilder& WithAbsorb(uint32_t absorb) + { + _stub.SetAbsorb(absorb); + return *this; + } + + DamageInfoStubBuilder& WithResist(uint32_t resist) + { + _stub.SetResist(resist); + return *this; + } + + DamageInfoStubBuilder& WithBlock(uint32_t block) + { + _stub.SetBlock(block); + return *this; + } + + DamageInfoStubBuilder& WithHitMask(uint32_t hitMask) + { + _stub.SetHitMask(hitMask); + return *this; + } + + DamageInfoStub Build() { return _stub; } + +private: + DamageInfoStub _stub; +}; + +/** + * @brief Builder for creating HealInfoStub instances with fluent API + */ +class HealInfoStubBuilder +{ +public: + HealInfoStubBuilder() = default; + + HealInfoStubBuilder& WithHeal(uint32_t heal) + { + _stub.SetHeal(heal); + _stub.SetEffectiveHeal(heal); // Assume all effective unless overridden + return *this; + } + + HealInfoStubBuilder& WithEffectiveHeal(uint32_t effectiveHeal) + { + _stub.SetEffectiveHeal(effectiveHeal); + return *this; + } + + HealInfoStubBuilder& WithOverheal(uint32_t overheal) + { + // Overheal = Heal - EffectiveHeal + // So EffectiveHeal = Heal - Overheal + if (_stub.GetHeal() >= overheal) + { + _stub.SetEffectiveHeal(_stub.GetHeal() - overheal); + } + return *this; + } + + HealInfoStubBuilder& WithAbsorb(uint32_t absorb) + { + _stub.SetAbsorb(absorb); + return *this; + } + + HealInfoStubBuilder& WithSpellInfo(SpellInfo const* spellInfo) + { + _stub.SetSpellInfo(spellInfo); + return *this; + } + + HealInfoStubBuilder& WithHitMask(uint32_t hitMask) + { + _stub.SetHitMask(hitMask); + return *this; + } + + HealInfoStub Build() { return _stub; } + +private: + HealInfoStub _stub; +}; + +#endif //AZEROTHCORE_DAMAGE_HEAL_INFO_STUB_H diff --git a/src/test/mocks/ProcChanceTestHelper.h b/src/test/mocks/ProcChanceTestHelper.h new file mode 100644 index 000000000..58436973d --- /dev/null +++ b/src/test/mocks/ProcChanceTestHelper.h @@ -0,0 +1,565 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H +#define AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H + +#include "SpellMgr.h" +#include "SpellInfo.h" +#include "AuraStub.h" +#include "UnitStub.h" +#include +#include + +/** + * @brief Helper class for testing proc chance calculations + * + * Provides standalone implementations of proc-related calculations + * that can be tested without requiring full game objects. + */ +class ProcChanceTestHelper +{ +public: + /** + * @brief Calculate PPM proc chance + * Implements the formula: (WeaponSpeed * PPM) / 600.0f + * + * @param weaponSpeed Weapon attack speed in milliseconds + * @param ppm Procs per minute value + * @param ppmModifier Additional PPM modifier (from talents/auras) + * @return Proc chance as percentage (0-100+) + */ + static float CalculatePPMChance(uint32 weaponSpeed, float ppm, float ppmModifier = 0.0f) + { + if (ppm <= 0.0f) + return 0.0f; + + float modifiedPPM = ppm + ppmModifier; + return (static_cast(weaponSpeed) * modifiedPPM) / 600.0f; + } + + /** + * @brief Calculate level 60+ reduction + * Implements PROC_ATTR_REDUCE_PROC_60: 3.333% reduction per level above 60 + * + * @param baseChance Base proc chance + * @param actorLevel Actor's level + * @return Reduced proc chance + */ + static float ApplyLevel60Reduction(float baseChance, uint32 actorLevel) + { + if (actorLevel <= 60) + return baseChance; + + // Reduction = (level - 60) / 30, capped at 1.0 + float reduction = static_cast(actorLevel - 60) / 30.0f; + return std::max(0.0f, (1.0f - reduction) * baseChance); + } + + /** + * @brief Simulate CalcProcChance() from SpellAuras.cpp + * + * @param procEntry The proc configuration + * @param actorLevel Actor's level (for PROC_ATTR_REDUCE_PROC_60) + * @param weaponSpeed Weapon speed (for PPM calculation) + * @param chanceModifier Talent/aura modifier to chance + * @param ppmModifier Talent/aura modifier to PPM + * @param hasDamageInfo Whether a DamageInfo is present (enables PPM) + * @return Calculated proc chance + */ + static float SimulateCalcProcChance( + SpellProcEntry const& procEntry, + uint32 actorLevel = 80, + uint32 weaponSpeed = 2500, + float chanceModifier = 0.0f, + float ppmModifier = 0.0f, + bool hasDamageInfo = true) + { + float chance = procEntry.Chance; + + // PPM calculation overrides base chance if PPM > 0 and we have DamageInfo + if (hasDamageInfo && procEntry.ProcsPerMinute > 0.0f) + { + chance = CalculatePPMChance(weaponSpeed, procEntry.ProcsPerMinute, ppmModifier); + } + + // Apply chance modifier (SPELLMOD_CHANCE_OF_SUCCESS) + chance += chanceModifier; + + // Apply level 60+ reduction if attribute is set + if (procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60) + { + chance = ApplyLevel60Reduction(chance, actorLevel); + } + + return chance; + } + + /** + * @brief Simulate charge consumption from ConsumeProcCharges() + * + * @param aura The aura stub to modify + * @param procEntry The proc configuration + * @return true if aura was removed (charges/stacks exhausted) + */ + static bool SimulateConsumeProcCharges(AuraStub* aura, SpellProcEntry const& procEntry) + { + if (!aura) + return false; + + if (procEntry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES) + { + return aura->ModStackAmount(-1); + } + else if (aura->IsUsingCharges()) + { + aura->DropCharge(); + if (aura->GetCharges() == 0) + { + aura->Remove(); + return true; + } + } + return false; + } + + /** + * @brief Check if proc is on cooldown + * + * @param aura The aura stub + * @param now Current time point + * @return true if proc is blocked by cooldown + */ + static bool IsProcOnCooldown(AuraStub const* aura, std::chrono::steady_clock::time_point now) + { + if (!aura) + return false; + return aura->IsProcOnCooldown(now); + } + + /** + * @brief Apply proc cooldown to aura + * + * @param aura The aura stub + * @param now Current time point + * @param cooldownMs Cooldown duration in milliseconds + */ + static void ApplyProcCooldown(AuraStub* aura, std::chrono::steady_clock::time_point now, uint32 cooldownMs) + { + if (!aura || cooldownMs == 0) + return; + aura->AddProcCooldown(now + std::chrono::milliseconds(cooldownMs)); + } + + /** + * @brief Check if spell has mana cost (for PROC_ATTR_REQ_MANA_COST) + * + * @param spellInfo The spell info to check + * @return true if spell has mana cost > 0 + */ + static bool SpellHasManaCost(SpellInfo const* spellInfo) + { + if (!spellInfo) + return false; + return spellInfo->ManaCost > 0 || spellInfo->ManaCostPercentage > 0; + } + + /** + * @brief Get common weapon speeds for testing + */ + static constexpr uint32 WEAPON_SPEED_FAST_DAGGER = 1400; // 1.4 sec + static constexpr uint32 WEAPON_SPEED_NORMAL_SWORD = 2500; // 2.5 sec + static constexpr uint32 WEAPON_SPEED_SLOW_2H = 3300; // 3.3 sec + static constexpr uint32 WEAPON_SPEED_VERY_SLOW = 3800; // 3.8 sec + static constexpr uint32 WEAPON_SPEED_STAFF = 3600; // 3.6 sec (common feral staff) + + /** + * @brief Shapeshift form base attack speeds (from SpellShapeshiftForm.dbc) + */ + static constexpr uint32 FORM_SPEED_CAT = 1000; // Cat Form: 1.0 sec + static constexpr uint32 FORM_SPEED_BEAR = 2500; // Bear/Dire Bear: 2.5 sec + + /** + * @brief Simulate effective procs per minute + * + * Given a per-swing chance and the actual swing interval, calculate + * how many procs occur per minute on average. + * + * @param chancePerSwing Proc chance per swing (0-100+) + * @param actualSwingSpeedMs Actual time between swings in milliseconds + * @return Average procs per minute + */ + static float CalculateEffectivePPM(float chancePerSwing, uint32 actualSwingSpeedMs) + { + if (actualSwingSpeedMs == 0) + return 0.0f; + float swingsPerMinute = 60000.0f / static_cast(actualSwingSpeedMs); + return swingsPerMinute * (chancePerSwing / 100.0f); + } + + /** + * @brief Common PPM values from spell_proc database + */ + static constexpr float PPM_OMEN_OF_CLARITY = 6.0f; + static constexpr float PPM_JUDGEMENT_OF_LIGHT = 15.0f; + static constexpr float PPM_WINDFURY_WEAPON = 2.0f; + + // ============================================================================= + // Triggered Spell Filtering - simulates SpellAuras.cpp:2191-2209 + // ============================================================================= + + /** + * @brief Auto-attack proc flag mask (hunter auto-shot, wands exception) + * These triggered spells are allowed to proc without TRIGGERED_CAN_PROC + */ + static constexpr uint32 AUTO_ATTACK_PROC_FLAG_MASK = + PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK | + PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK; + + /** + * @brief Configuration for simulating triggered spell filtering + */ + struct TriggeredSpellConfig + { + bool isTriggered = false; // Spell::IsTriggered() + bool auraHasCanProcFromProcs = false; // SPELL_ATTR3_CAN_PROC_FROM_PROCS on proc aura + bool spellHasNotAProc = false; // SPELL_ATTR3_NOT_A_PROC on triggering spell + uint32 triggeredByAuraSpellId = 0; // GetTriggeredByAuraSpellInfo()->Id + uint32 procAuraSpellId = 0; // The aura's spell ID (for self-loop check) + }; + + /** + * @brief Simulate triggered spell filtering + * Implements the self-loop prevention and triggered spell blocking from SpellAuras.cpp + * + * @param config Configuration for the triggered spell + * @param procEntry The proc entry being checked + * @param eventTypeMask The event type mask from ProcEventInfo + * @return true if proc should be blocked (return 0), false if allowed + */ + static bool ShouldBlockTriggeredSpell( + TriggeredSpellConfig const& config, + SpellProcEntry const& procEntry, + uint32 eventTypeMask) + { + // Self-loop prevention: block if triggered by the same aura + // SpellAuras.cpp:2191-2192 + if (config.triggeredByAuraSpellId != 0 && + config.triggeredByAuraSpellId == config.procAuraSpellId) + { + return true; // Block: self-loop detected + } + + // Check if triggered spell filtering applies + // SpellAuras.cpp:2195-2208 + if (!config.auraHasCanProcFromProcs && + !(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) && + !(eventTypeMask & AUTO_ATTACK_PROC_FLAG_MASK)) + { + // Filter triggered spells unless they have NOT_A_PROC + if (config.isTriggered && !config.spellHasNotAProc) + { + return true; // Block: triggered spell without exceptions + } + } + + return false; // Allow proc + } + + // ============================================================================= + // DisableEffectsMask - simulates SpellAuras.cpp:2244-2258 + // ============================================================================= + + /** + * @brief Apply DisableEffectsMask to get final proc effect mask + * + * @param initialMask Initial effect mask (usually 0x07 for all 3 effects) + * @param disableEffectsMask Mask of effects to disable + * @return Resulting effect mask after applying disable mask + */ + static uint8 ApplyDisableEffectsMask(uint8 initialMask, uint32 disableEffectsMask) + { + uint8 result = initialMask; + for (uint8 i = 0; i < 3; ++i) + { + if (disableEffectsMask & (1u << i)) + result &= ~(1 << i); + } + return result; + } + + /** + * @brief Check if proc should be blocked due to all effects being disabled + * + * @param initialMask Initial effect mask + * @param disableEffectsMask Mask of effects to disable + * @return true if all effects disabled (proc blocked), false otherwise + */ + static bool ShouldBlockDueToDisabledEffects(uint8 initialMask, uint32 disableEffectsMask) + { + return ApplyDisableEffectsMask(initialMask, disableEffectsMask) == 0; + } + + // ============================================================================= + // PPM Modifier Simulation - simulates Unit.cpp:10378-10390 + // ============================================================================= + + /** + * @brief PPM modifier configuration for testing SPELLMOD_PROC_PER_MINUTE + */ + struct PPMModifierConfig + { + float flatModifier = 0.0f; // Additive PPM modifier + float pctModifier = 1.0f; // Multiplicative PPM modifier (1.0 = no change) + bool hasSpellModOwner = true; // Whether GetSpellModOwner() returns valid player + bool hasSpellProto = true; // Whether spellProto is provided + }; + + /** + * @brief Calculate PPM chance with spell modifiers + * Simulates GetPPMProcChance() with SPELLMOD_PROC_PER_MINUTE + */ + static float CalculatePPMChanceWithModifiers( + uint32 weaponSpeed, + float basePPM, + PPMModifierConfig const& modConfig) + { + if (basePPM <= 0.0f) + return 0.0f; + + float ppm = basePPM; + + // Apply modifiers only if we have spell proto and spell mod owner + if (modConfig.hasSpellProto && modConfig.hasSpellModOwner) + { + // Apply flat modifier first (SPELLMOD_FLAT) + ppm += modConfig.flatModifier; + // Apply percent modifier (SPELLMOD_PCT) + ppm *= modConfig.pctModifier; + } + + return (static_cast(weaponSpeed) * ppm) / 600.0f; + } + + // ============================================================================= + // Equipment Requirements - simulates SpellAuras.cpp:2260-2298 + // ============================================================================= + + /** + * @brief Item classes for equipment requirement checking + */ + static constexpr int32 ITEM_CLASS_WEAPON = 2; + static constexpr int32 ITEM_CLASS_ARMOR = 4; + static constexpr int32 ITEM_CLASS_ANY = -1; // No requirement + + /** + * @brief Attack types for weapon slot mapping + */ + static constexpr uint8 BASE_ATTACK = 0; + static constexpr uint8 OFF_ATTACK = 1; + static constexpr uint8 RANGED_ATTACK = 2; + + /** + * @brief Configuration for simulating equipment requirements + */ + struct EquipmentConfig + { + bool isPassive = true; // Aura::IsPassive() + bool isPlayer = true; // Target is player + int32 equippedItemClass = ITEM_CLASS_ANY; // SpellInfo::EquippedItemClass + int32 equippedItemSubClassMask = 0; // SpellInfo::EquippedItemSubClassMask + bool hasNoEquipRequirementAttr = false; // SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT + uint8 attackType = BASE_ATTACK; // Attack type for weapon slot mapping + bool isInFeralForm = false; // Player::IsInFeralForm() + bool hasEquippedItem = true; // Item is equipped in the slot + bool itemIsBroken = false; // Item::IsBroken() + bool itemFitsRequirements = true; // Item::IsFitToSpellRequirements() + }; + + /** + * @brief Simulate equipment requirement check + * Returns true if proc should be blocked due to equipment requirements + * + * @param config Equipment configuration + * @return true if proc should be blocked + */ + static bool ShouldBlockDueToEquipment(EquipmentConfig const& config) + { + // Only check for passive player auras with equipment requirements + if (!config.isPassive || !config.isPlayer || config.equippedItemClass == ITEM_CLASS_ANY) + return false; + + // SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT bypasses check + if (config.hasNoEquipRequirementAttr) + return false; + + // Feral form blocks weapon procs + if (config.equippedItemClass == ITEM_CLASS_WEAPON && config.isInFeralForm) + return true; + + // No item equipped in the required slot + if (!config.hasEquippedItem) + return true; + + // Item is broken + if (config.itemIsBroken) + return true; + + // Item doesn't fit spell requirements (wrong subclass, etc.) + if (!config.itemFitsRequirements) + return true; + + return false; + } + + /** + * @brief Get equipment slot for attack type + * + * @param attackType Attack type (BASE_ATTACK, OFF_ATTACK, RANGED_ATTACK) + * @return Equipment slot index (simulated) + */ + static uint8 GetWeaponSlotForAttackType(uint8 attackType) + { + switch (attackType) + { + case BASE_ATTACK: + return 15; // EQUIPMENT_SLOT_MAINHAND + case OFF_ATTACK: + return 16; // EQUIPMENT_SLOT_OFFHAND + case RANGED_ATTACK: + return 17; // EQUIPMENT_SLOT_RANGED + default: + return 15; + } + } + + // ============================================================================= + // Conditions System - simulates SpellAuras.cpp:2232-2236 + // ============================================================================= + + /** + * @brief Configuration for simulating conditions system + */ + struct ConditionsConfig + { + bool hasConditions = false; // ConditionMgr has conditions for this spell + bool conditionsMet = true; // All conditions are satisfied + uint32 sourceType = 24; // CONDITION_SOURCE_TYPE_SPELL_PROC + }; + + /** + * @brief Simulate conditions check + * Returns true if proc should be blocked due to conditions + * + * @param config Conditions configuration + * @return true if proc should be blocked + */ + static bool ShouldBlockDueToConditions(ConditionsConfig const& config) + { + // No conditions configured - allow proc + if (!config.hasConditions) + return false; + + // Check if conditions are met + return !config.conditionsMet; + } +}; + +/** + * @brief Test context for proc simulation scenarios + */ +class ProcTestScenario +{ +public: + ProcTestScenario() : _now(std::chrono::steady_clock::now()) {} + + // Time control + void AdvanceTime(std::chrono::milliseconds duration) + { + _now += duration; + } + + std::chrono::steady_clock::time_point GetNow() const { return _now; } + + // Actor configuration + UnitStub& GetActor() { return _actor; } + UnitStub const& GetActor() const { return _actor; } + + ProcTestScenario& WithActorLevel(uint8_t level) + { + _actor.SetLevel(level); + return *this; + } + + ProcTestScenario& WithWeaponSpeed(uint8_t attackType, uint32_t speed) + { + _actor.SetAttackTime(attackType, speed); + return *this; + } + + // Aura configuration + std::unique_ptr& GetAura() { return _aura; } + + ProcTestScenario& WithAura(uint32_t spellId, uint8_t charges = 0, uint8_t stacks = 1) + { + _aura = std::make_unique(spellId); + _aura->SetCharges(charges); + _aura->SetUsingCharges(charges > 0); + _aura->SetStackAmount(stacks); + return *this; + } + + // Simulate proc and return whether it triggered + bool SimulateProc(SpellProcEntry const& procEntry, float rollResult = 0.0f) + { + if (!_aura) + return false; + + // Check cooldown + if (ProcChanceTestHelper::IsProcOnCooldown(_aura.get(), _now)) + return false; + + // Calculate chance + float chance = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, + _actor.GetLevel(), + _actor.GetAttackTime(0)); + + // Roll check (rollResult of 0 means always pass) + if (rollResult > 0.0f && rollResult > chance) + return false; + + // Apply cooldown if set + if (procEntry.Cooldown.count() > 0) + { + ProcChanceTestHelper::ApplyProcCooldown(_aura.get(), _now, + static_cast(procEntry.Cooldown.count())); + } + + // Consume charges + ProcChanceTestHelper::SimulateConsumeProcCharges(_aura.get(), procEntry); + + return true; + } + +private: + std::chrono::steady_clock::time_point _now; + UnitStub _actor; + std::unique_ptr _aura; +}; + +#endif // AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H diff --git a/src/test/mocks/ProcEventInfoHelper.h b/src/test/mocks/ProcEventInfoHelper.h new file mode 100644 index 000000000..41e9275b8 --- /dev/null +++ b/src/test/mocks/ProcEventInfoHelper.h @@ -0,0 +1,235 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_PROC_EVENT_INFO_HELPER_H +#define AZEROTHCORE_PROC_EVENT_INFO_HELPER_H + +#include "SpellMgr.h" +#include "Unit.h" + +/** + * @brief Builder class for creating ProcEventInfo test instances + * + * This helper allows easy construction of ProcEventInfo objects for unit testing + * the proc system without requiring full game objects. + */ +class ProcEventInfoBuilder +{ +public: + ProcEventInfoBuilder() + : _actor(nullptr), _actionTarget(nullptr), _procTarget(nullptr), + _typeMask(0), _spellTypeMask(0), _spellPhaseMask(0), _hitMask(0), + _spell(nullptr), _damageInfo(nullptr), _healInfo(nullptr), + _triggeredByAuraSpell(nullptr), _procAuraEffectIndex(-1) {} + + ProcEventInfoBuilder& WithActor(Unit* actor) + { + _actor = actor; + return *this; + } + + ProcEventInfoBuilder& WithActionTarget(Unit* target) + { + _actionTarget = target; + return *this; + } + + ProcEventInfoBuilder& WithProcTarget(Unit* target) + { + _procTarget = target; + return *this; + } + + ProcEventInfoBuilder& WithTypeMask(uint32 typeMask) + { + _typeMask = typeMask; + return *this; + } + + ProcEventInfoBuilder& WithSpellTypeMask(uint32 spellTypeMask) + { + _spellTypeMask = spellTypeMask; + return *this; + } + + ProcEventInfoBuilder& WithSpellPhaseMask(uint32 spellPhaseMask) + { + _spellPhaseMask = spellPhaseMask; + return *this; + } + + ProcEventInfoBuilder& WithHitMask(uint32 hitMask) + { + _hitMask = hitMask; + return *this; + } + + ProcEventInfoBuilder& WithSpell(Spell const* spell) + { + _spell = spell; + return *this; + } + + ProcEventInfoBuilder& WithDamageInfo(DamageInfo* damageInfo) + { + _damageInfo = damageInfo; + return *this; + } + + ProcEventInfoBuilder& WithHealInfo(HealInfo* healInfo) + { + _healInfo = healInfo; + return *this; + } + + ProcEventInfoBuilder& WithTriggeredByAuraSpell(SpellInfo const* spellInfo) + { + _triggeredByAuraSpell = spellInfo; + return *this; + } + + ProcEventInfoBuilder& WithProcAuraEffectIndex(int8 index) + { + _procAuraEffectIndex = index; + return *this; + } + + ProcEventInfo Build() + { + return ProcEventInfo(_actor, _actionTarget, _procTarget, _typeMask, + _spellTypeMask, _spellPhaseMask, _hitMask, + _spell, _damageInfo, _healInfo, + _triggeredByAuraSpell, _procAuraEffectIndex); + } + +private: + Unit* _actor; + Unit* _actionTarget; + Unit* _procTarget; + uint32 _typeMask; + uint32 _spellTypeMask; + uint32 _spellPhaseMask; + uint32 _hitMask; + Spell const* _spell; + DamageInfo* _damageInfo; + HealInfo* _healInfo; + SpellInfo const* _triggeredByAuraSpell; + int8 _procAuraEffectIndex; +}; + +/** + * @brief Builder class for creating SpellProcEntry test instances + * + * This helper allows easy construction of SpellProcEntry objects for unit testing + * the proc system. + */ +class SpellProcEntryBuilder +{ +public: + SpellProcEntryBuilder() + { + _entry = {}; + } + + SpellProcEntryBuilder& WithSchoolMask(uint32 schoolMask) + { + _entry.SchoolMask = schoolMask; + return *this; + } + + SpellProcEntryBuilder& WithSpellFamilyName(uint32 familyName) + { + _entry.SpellFamilyName = familyName; + return *this; + } + + SpellProcEntryBuilder& WithSpellFamilyMask(flag96 familyMask) + { + _entry.SpellFamilyMask = familyMask; + return *this; + } + + SpellProcEntryBuilder& WithProcFlags(uint32 procFlags) + { + _entry.ProcFlags = procFlags; + return *this; + } + + SpellProcEntryBuilder& WithSpellTypeMask(uint32 spellTypeMask) + { + _entry.SpellTypeMask = spellTypeMask; + return *this; + } + + SpellProcEntryBuilder& WithSpellPhaseMask(uint32 spellPhaseMask) + { + _entry.SpellPhaseMask = spellPhaseMask; + return *this; + } + + SpellProcEntryBuilder& WithHitMask(uint32 hitMask) + { + _entry.HitMask = hitMask; + return *this; + } + + SpellProcEntryBuilder& WithAttributesMask(uint32 attributesMask) + { + _entry.AttributesMask = attributesMask; + return *this; + } + + SpellProcEntryBuilder& WithDisableEffectsMask(uint32 disableEffectsMask) + { + _entry.DisableEffectsMask = disableEffectsMask; + return *this; + } + + SpellProcEntryBuilder& WithProcsPerMinute(float ppm) + { + _entry.ProcsPerMinute = ppm; + return *this; + } + + SpellProcEntryBuilder& WithChance(float chance) + { + _entry.Chance = chance; + return *this; + } + + SpellProcEntryBuilder& WithCooldown(Milliseconds cooldown) + { + _entry.Cooldown = cooldown; + return *this; + } + + SpellProcEntryBuilder& WithCharges(uint32 charges) + { + _entry.Charges = charges; + return *this; + } + + SpellProcEntry Build() const + { + return _entry; + } + +private: + SpellProcEntry _entry; +}; + +#endif //AZEROTHCORE_PROC_EVENT_INFO_HELPER_H diff --git a/src/test/mocks/SpellInfoTestHelper.h b/src/test/mocks/SpellInfoTestHelper.h new file mode 100644 index 000000000..ce129b4d1 --- /dev/null +++ b/src/test/mocks/SpellInfoTestHelper.h @@ -0,0 +1,215 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_SPELL_INFO_TEST_HELPER_H +#define AZEROTHCORE_SPELL_INFO_TEST_HELPER_H + +#include "SpellInfo.h" +#include "SharedDefines.h" +#include + +/** + * @brief Helper class to create SpellEntry test instances + * + * This creates a SpellEntry with sensible defaults for unit testing. + */ +class TestSpellEntryHelper +{ +public: + TestSpellEntryHelper() + { + // Zero initialize all fields + std::memset(&_entry, 0, sizeof(_entry)); + + // Set safe defaults + _entry.EquippedItemClass = -1; + _entry.SchoolMask = SPELL_SCHOOL_MASK_NORMAL; + + // Initialize empty strings + for (auto& name : _entry.SpellName) + name = ""; + for (auto& rank : _entry.Rank) + rank = ""; + } + + TestSpellEntryHelper& WithId(uint32 id) + { + _entry.Id = id; + return *this; + } + + TestSpellEntryHelper& WithSpellFamilyName(uint32 familyName) + { + _entry.SpellFamilyName = familyName; + return *this; + } + + TestSpellEntryHelper& WithSpellFamilyFlags(uint32 flag0, uint32 flag1 = 0, uint32 flag2 = 0) + { + _entry.SpellFamilyFlags[0] = flag0; + _entry.SpellFamilyFlags[1] = flag1; + _entry.SpellFamilyFlags[2] = flag2; + return *this; + } + + TestSpellEntryHelper& WithSchoolMask(uint32 schoolMask) + { + _entry.SchoolMask = schoolMask; + return *this; + } + + TestSpellEntryHelper& WithProcFlags(uint32 procFlags) + { + _entry.ProcFlags = procFlags; + return *this; + } + + TestSpellEntryHelper& WithProcChance(uint32 procChance) + { + _entry.ProcChance = procChance; + return *this; + } + + TestSpellEntryHelper& WithProcCharges(uint32 procCharges) + { + _entry.ProcCharges = procCharges; + return *this; + } + + TestSpellEntryHelper& WithDmgClass(uint32 dmgClass) + { + _entry.DmgClass = dmgClass; + return *this; + } + + TestSpellEntryHelper& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0) + { + if (effIndex < MAX_SPELL_EFFECTS) + { + _entry.Effect[effIndex] = effect; + _entry.EffectApplyAuraName[effIndex] = auraType; + } + return *this; + } + + TestSpellEntryHelper& WithEffectTriggerSpell(uint8 effIndex, uint32 triggerSpell) + { + if (effIndex < MAX_SPELL_EFFECTS) + { + _entry.EffectTriggerSpell[effIndex] = triggerSpell; + } + return *this; + } + + SpellEntry const* Get() const + { + return &_entry; + } + +private: + SpellEntry _entry; +}; + +/** + * @brief Builder class for creating SpellInfo test instances + * + * This helper allows easy construction of SpellInfo objects for unit testing + * without requiring DBC data. + */ +class SpellInfoBuilder +{ +public: + SpellInfoBuilder() : _entryHelper() {} + + SpellInfoBuilder& WithId(uint32 id) + { + _entryHelper.WithId(id); + return *this; + } + + SpellInfoBuilder& WithSpellFamilyName(uint32 familyName) + { + _entryHelper.WithSpellFamilyName(familyName); + return *this; + } + + SpellInfoBuilder& WithSpellFamilyFlags(uint32 flag0, uint32 flag1 = 0, uint32 flag2 = 0) + { + _entryHelper.WithSpellFamilyFlags(flag0, flag1, flag2); + return *this; + } + + SpellInfoBuilder& WithSchoolMask(uint32 schoolMask) + { + _entryHelper.WithSchoolMask(schoolMask); + return *this; + } + + SpellInfoBuilder& WithProcFlags(uint32 procFlags) + { + _entryHelper.WithProcFlags(procFlags); + return *this; + } + + SpellInfoBuilder& WithProcChance(uint32 procChance) + { + _entryHelper.WithProcChance(procChance); + return *this; + } + + SpellInfoBuilder& WithProcCharges(uint32 procCharges) + { + _entryHelper.WithProcCharges(procCharges); + return *this; + } + + SpellInfoBuilder& WithDmgClass(uint32 dmgClass) + { + _entryHelper.WithDmgClass(dmgClass); + return *this; + } + + SpellInfoBuilder& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0) + { + _entryHelper.WithEffect(effIndex, effect, auraType); + return *this; + } + + SpellInfoBuilder& WithEffectTriggerSpell(uint8 effIndex, uint32 triggerSpell) + { + _entryHelper.WithEffectTriggerSpell(effIndex, triggerSpell); + return *this; + } + + // Builds and returns a SpellInfo pointer + // Note: Caller is responsible for lifetime management + SpellInfo* Build() + { + return new SpellInfo(_entryHelper.Get()); + } + + // Builds and returns a managed SpellInfo pointer + std::unique_ptr BuildUnique() + { + return std::unique_ptr(new SpellInfo(_entryHelper.Get())); + } + +private: + TestSpellEntryHelper _entryHelper; +}; + +#endif //AZEROTHCORE_SPELL_INFO_TEST_HELPER_H diff --git a/src/test/mocks/UnitStub.h b/src/test/mocks/UnitStub.h new file mode 100644 index 000000000..001c65af5 --- /dev/null +++ b/src/test/mocks/UnitStub.h @@ -0,0 +1,244 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef AZEROTHCORE_UNIT_STUB_H +#define AZEROTHCORE_UNIT_STUB_H + +#include "gmock/gmock.h" +#include +#include +#include +#include + +class SpellInfo; +class Aura; +class AuraEffect; +class Item; + +/** + * @brief Lightweight stub for Unit proc-related functionality + * + * This stub provides controlled behavior for testing proc scripts + * without requiring the full Unit hierarchy. + */ +class UnitStub +{ +public: + UnitStub() = default; + virtual ~UnitStub() = default; + + // Identity + virtual bool IsPlayer() const { return _isPlayer; } + virtual bool IsAlive() const { return _isAlive; } + virtual bool IsFriendlyTo(UnitStub const* unit) const { return _isFriendly; } + + void SetIsPlayer(bool isPlayer) { _isPlayer = isPlayer; } + void SetIsAlive(bool isAlive) { _isAlive = isAlive; } + void SetIsFriendly(bool isFriendly) { _isFriendly = isFriendly; } + + // Aura management + virtual bool HasAura(uint32_t spellId) const + { + return _auras.find(spellId) != _auras.end(); + } + + virtual void AddAuraStub(uint32_t spellId) + { + _auras[spellId] = true; + } + + virtual void RemoveAuraStub(uint32_t spellId) + { + _auras.erase(spellId); + } + + // Spell casting tracking + struct CastRecord + { + uint32_t spellId; + bool triggered; + int32_t bp0; + int32_t bp1; + int32_t bp2; + }; + + virtual void RecordCast(uint32_t spellId, bool triggered = true, + int32_t bp0 = 0, int32_t bp1 = 0, int32_t bp2 = 0) + { + _castHistory.push_back({spellId, triggered, bp0, bp1, bp2}); + } + + [[nodiscard]] std::vector const& GetCastHistory() const { return _castHistory; } + + [[nodiscard]] bool WasCast(uint32_t spellId) const + { + for (auto const& record : _castHistory) + { + if (record.spellId == spellId) + return true; + } + return false; + } + + [[nodiscard]] size_t CountCasts(uint32_t spellId) const + { + size_t count = 0; + for (auto const& record : _castHistory) + { + if (record.spellId == spellId) + ++count; + } + return count; + } + + void ClearCastHistory() { _castHistory.clear(); } + + // Health/Power + virtual uint32_t GetMaxHealth() const { return _maxHealth; } + virtual uint32_t GetHealth() const { return _health; } + virtual uint32_t CountPctFromMaxHealth(int32_t pct) const + { + return (_maxHealth * static_cast(pct)) / 100; + } + + void SetMaxHealth(uint32_t maxHealth) { _maxHealth = maxHealth; } + void SetHealth(uint32_t health) { _health = health; } + + // Weapon speed for PPM calculations + virtual uint32_t GetAttackTime(uint8_t attType) const + { + return _attackTimes.count(attType) ? _attackTimes.at(attType) : 2000; + } + + void SetAttackTime(uint8_t attType, uint32_t time) + { + _attackTimes[attType] = time; + } + + // PPM modifier tracking for proc tests + // Simulates Player::ApplySpellMod(spellId, SPELLMOD_PROC_PER_MINUTE, ppm) + void SetPPMModifier(uint32_t spellId, float modifier) + { + _ppmModifiers[spellId] = modifier; + } + + void ClearPPMModifiers() + { + _ppmModifiers.clear(); + } + + /** + * @brief Calculate PPM proc chance with modifiers + * Mimics Unit::GetPPMProcChance() formula: (WeaponSpeed * PPM) / 600.0f + */ + virtual float GetPPMProcChance(uint32_t weaponSpeed, float ppm, uint32_t spellId = 0) const + { + if (ppm <= 0.0f) + return 0.0f; + + // Apply PPM modifier if set for this spell + float modifiedPPM = ppm; + if (spellId > 0 && _ppmModifiers.count(spellId)) + modifiedPPM += _ppmModifiers.at(spellId); + + return (static_cast(weaponSpeed) * modifiedPPM) / 600.0f; + } + + // Chance modifier tracking for proc tests + // Simulates Player::ApplySpellMod(spellId, SPELLMOD_CHANCE_OF_SUCCESS, chance) + void SetChanceModifier(uint32_t spellId, float modifier) + { + _chanceModifiers[spellId] = modifier; + } + + void ClearChanceModifiers() + { + _chanceModifiers.clear(); + } + + /** + * @brief Apply chance modifier for a spell + */ + float ApplyChanceModifier(uint32_t spellId, float baseChance) const + { + if (spellId > 0 && _chanceModifiers.count(spellId)) + return baseChance + _chanceModifiers.at(spellId); + return baseChance; + } + + // Cooldowns + virtual bool HasSpellCooldown(uint32_t spellId) const + { + return _cooldowns.find(spellId) != _cooldowns.end(); + } + + virtual void AddSpellCooldown(uint32_t spellId) + { + _cooldowns[spellId] = true; + } + + virtual void RemoveSpellCooldown(uint32_t spellId) + { + _cooldowns.erase(spellId); + } + + // Class/Level + virtual uint8_t GetClass() const { return _class; } + virtual uint8_t GetLevel() const { return _level; } + + void SetClass(uint8_t unitClass) { _class = unitClass; } + void SetLevel(uint8_t level) { _level = level; } + +private: + bool _isPlayer = false; + bool _isAlive = true; + bool _isFriendly = false; + + std::map _auras; + std::vector _castHistory; + std::map _cooldowns; + std::map _attackTimes; + std::map _ppmModifiers; // PPM modifiers by spell ID + std::map _chanceModifiers; // Chance modifiers by spell ID + + uint32_t _maxHealth = 10000; + uint32_t _health = 10000; + uint8_t _class = 1; // Warrior by default + uint8_t _level = 80; +}; + +/** + * @brief GMock-enabled Unit stub for verification + */ +class MockUnitStub : public UnitStub +{ +public: + MOCK_METHOD(bool, IsPlayer, (), (const, override)); + MOCK_METHOD(bool, IsAlive, (), (const, override)); + MOCK_METHOD(bool, IsFriendlyTo, (UnitStub const* unit), (const, override)); + MOCK_METHOD(bool, HasAura, (uint32_t spellId), (const, override)); + MOCK_METHOD(uint32_t, GetMaxHealth, (), (const, override)); + MOCK_METHOD(uint32_t, GetHealth, (), (const, override)); + MOCK_METHOD(uint32_t, CountPctFromMaxHealth, (int32_t pct), (const, override)); + MOCK_METHOD(uint32_t, GetAttackTime, (uint8_t attType), (const, override)); + MOCK_METHOD(bool, HasSpellCooldown, (uint32_t spellId), (const, override)); + MOCK_METHOD(uint8_t, GetClass, (), (const, override)); + MOCK_METHOD(uint8_t, GetLevel, (), (const, override)); + MOCK_METHOD(float, GetPPMProcChance, (uint32_t weaponSpeed, float ppm, uint32_t spellId), (const, override)); +}; + +#endif //AZEROTHCORE_UNIT_STUB_H diff --git a/src/test/server/game/Spells/SpellProcAttributeTest.cpp b/src/test/server/game/Spells/SpellProcAttributeTest.cpp new file mode 100644 index 000000000..536c105d8 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcAttributeTest.cpp @@ -0,0 +1,445 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcAttributeTest.cpp + * @brief Unit tests for PROC_ATTR_* flags + * + * Tests all proc attribute flags: + * - PROC_ATTR_REQ_EXP_OR_HONOR (0x01) + * - PROC_ATTR_TRIGGERED_CAN_PROC (0x02) + * - PROC_ATTR_REQ_MANA_COST (0x04) + * - PROC_ATTR_REQ_SPELLMOD (0x08) + * - PROC_ATTR_USE_STACKS_FOR_CHARGES (0x10) + * - PROC_ATTR_REDUCE_PROC_60 (0x80) + * - PROC_ATTR_CANT_PROC_FROM_ITEM_CAST (0x100) + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "AuraStub.h" +#include "UnitStub.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcAttributeTest : public ::testing::Test +{ +protected: + void SetUp() override {} +}; + +// ============================================================================= +// PROC_ATTR_REQ_EXP_OR_HONOR (0x01) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, ReqExpOrHonor_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_REQ_EXP_OR_HONOR) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_EXP_OR_HONOR); +} + +TEST_F(SpellProcAttributeTest, ReqExpOrHonor_AttributeNotSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(0) + .Build(); + + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_EXP_OR_HONOR); +} + +// ============================================================================= +// PROC_ATTR_TRIGGERED_CAN_PROC (0x02) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, TriggeredCanProc_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC); +} + +TEST_F(SpellProcAttributeTest, TriggeredCanProc_AttributeNotSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(0) + .Build(); + + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC); +} + +// ============================================================================= +// PROC_ATTR_REQ_MANA_COST (0x04) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, ReqManaCost_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_REQ_MANA_COST) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST); +} + +TEST_F(SpellProcAttributeTest, ReqManaCost_NullSpell_ShouldNotProc) +{ + // Null spell should never satisfy mana cost requirement + EXPECT_FALSE(ProcChanceTestHelper::SpellHasManaCost(nullptr)); +} + +// ============================================================================= +// PROC_ATTR_REQ_SPELLMOD (0x08) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, ReqSpellmod_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_REQ_SPELLMOD) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD); +} + +TEST_F(SpellProcAttributeTest, ReqSpellmod_AttributeNotSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(0) + .Build(); + + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD); +} + +// ============================================================================= +// PROC_ATTR_USE_STACKS_FOR_CHARGES (0x10) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, UseStacksForCharges_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES); +} + +TEST_F(SpellProcAttributeTest, UseStacksForCharges_DecrementStacks) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + EXPECT_EQ(aura->GetStackAmount(), 4); +} + +TEST_F(SpellProcAttributeTest, UseStacksForCharges_NotSet_DecrementCharges) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(5) + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(0) // No USE_STACKS_FOR_CHARGES + .Build(); + + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + // Charges should decrement, stacks unchanged + EXPECT_EQ(aura->GetCharges(), 4); + EXPECT_EQ(aura->GetStackAmount(), 5); +} + +// ============================================================================= +// PROC_ATTR_REDUCE_PROC_60 (0x80) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, ReduceProc60_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60); +} + +TEST_F(SpellProcAttributeTest, ReduceProc60_Level60_NoReduction) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float chance = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 60); + EXPECT_NEAR(chance, 30.0f, 0.01f); +} + +TEST_F(SpellProcAttributeTest, ReduceProc60_Level70_Reduced) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + // Level 70 = 10 levels above 60 + // Reduction = 10/30 = 33.33% + // 30% * (1 - 0.333) = 20% + float chance = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 70); + EXPECT_NEAR(chance, 20.0f, 0.5f); +} + +TEST_F(SpellProcAttributeTest, ReduceProc60_Level80_Reduced) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + // Level 80 = 20 levels above 60 + // Reduction = 20/30 = 66.67% + // 30% * (1 - 0.667) = 10% + float chance = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 80); + EXPECT_NEAR(chance, 10.0f, 0.5f); +} + +TEST_F(SpellProcAttributeTest, ReduceProc60_BelowLevel60_NoReduction) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float chance = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 50); + EXPECT_NEAR(chance, 30.0f, 0.01f); +} + +TEST_F(SpellProcAttributeTest, ReduceProc60_NotSet_NoReduction) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(0) // No REDUCE_PROC_60 + .Build(); + + // Even at level 80, no reduction without attribute + float chance = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 80); + EXPECT_NEAR(chance, 30.0f, 0.01f); +} + +// ============================================================================= +// PROC_ATTR_CANT_PROC_FROM_ITEM_CAST (0x100) Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, CantProcFromItemCast_AttributeSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_CANT_PROC_FROM_ITEM_CAST) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_CANT_PROC_FROM_ITEM_CAST); +} + +TEST_F(SpellProcAttributeTest, CantProcFromItemCast_AttributeNotSet) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(0) + .Build(); + + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_CANT_PROC_FROM_ITEM_CAST); +} + +// ============================================================================= +// Combined Attribute Tests +// ============================================================================= + +TEST_F(SpellProcAttributeTest, CombinedAttributes_MultipleFlags) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask( + PROC_ATTR_TRIGGERED_CAN_PROC | + PROC_ATTR_REQ_MANA_COST | + PROC_ATTR_REDUCE_PROC_60) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60); + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_REQ_EXP_OR_HONOR); + EXPECT_FALSE(procEntry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES); +} + +TEST_F(SpellProcAttributeTest, CombinedAttributes_AllFlags) +{ + uint32 allFlags = + PROC_ATTR_REQ_EXP_OR_HONOR | + PROC_ATTR_TRIGGERED_CAN_PROC | + PROC_ATTR_REQ_MANA_COST | + PROC_ATTR_REQ_SPELLMOD | + PROC_ATTR_USE_STACKS_FOR_CHARGES | + PROC_ATTR_REDUCE_PROC_60 | + PROC_ATTR_CANT_PROC_FROM_ITEM_CAST; + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(allFlags) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_EXP_OR_HONOR); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60); + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_CANT_PROC_FROM_ITEM_CAST); +} + +// ============================================================================= +// Real Spell Attribute Scenarios +// ============================================================================= + +TEST_F(SpellProcAttributeTest, Scenario_SealOfCommand_TriggeredCanProc) +{ + // Seal of Command (Paladin) can proc from triggered spells + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC); +} + +TEST_F(SpellProcAttributeTest, Scenario_ClearCasting_ReqManaCost) +{ + // Clearcasting (Mage/Priest) requires spell to have mana cost + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_REQ_MANA_COST) + .Build(); + + EXPECT_TRUE(procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST); + + // Null spell check - free/costless spells won't trigger + EXPECT_FALSE(ProcChanceTestHelper::SpellHasManaCost(nullptr)); +} + +TEST_F(SpellProcAttributeTest, Scenario_MaelstromWeapon_UseStacks) +{ + // Maelstrom Weapon (Shaman) uses stacks + auto aura = AuraStubBuilder() + .WithId(53817) + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // Each proc consumes one stack + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetStackAmount(), 4); + + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetStackAmount(), 3); +} + +TEST_F(SpellProcAttributeTest, Scenario_OldLevelScaling_ReduceProc60) +{ + // Some old vanilla/TBC procs have reduced chance at higher levels + auto procEntry = SpellProcEntryBuilder() + .WithChance(50.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + // Level 60: Full chance + float chanceAt60 = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 60); + EXPECT_NEAR(chanceAt60, 50.0f, 0.01f); + + // Level 75: 50% reduction (15/30) + float chanceAt75 = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 75); + EXPECT_NEAR(chanceAt75, 25.0f, 0.5f); + + // Level 90: 100% reduction (30/30), capped at 0 + float chanceAt90 = ProcChanceTestHelper::SimulateCalcProcChance(procEntry, 90); + EXPECT_NEAR(chanceAt90, 0.0f, 0.1f); +} + +// ============================================================================= +// Attribute Value Validation +// ============================================================================= + +TEST_F(SpellProcAttributeTest, AttributeValues_Correct) +{ + // Verify attribute flag values match expected hex values + EXPECT_EQ(PROC_ATTR_REQ_EXP_OR_HONOR, 0x0000001u); + EXPECT_EQ(PROC_ATTR_TRIGGERED_CAN_PROC, 0x0000002u); + EXPECT_EQ(PROC_ATTR_REQ_MANA_COST, 0x0000004u); + EXPECT_EQ(PROC_ATTR_REQ_SPELLMOD, 0x0000008u); + EXPECT_EQ(PROC_ATTR_USE_STACKS_FOR_CHARGES, 0x0000010u); + EXPECT_EQ(PROC_ATTR_REDUCE_PROC_60, 0x0000080u); + EXPECT_EQ(PROC_ATTR_CANT_PROC_FROM_ITEM_CAST, 0x0000100u); +} + +TEST_F(SpellProcAttributeTest, AttributeFlags_NonOverlapping) +{ + // Verify no two flags share the same bit + uint32 flags[] = { + PROC_ATTR_REQ_EXP_OR_HONOR, + PROC_ATTR_TRIGGERED_CAN_PROC, + PROC_ATTR_REQ_MANA_COST, + PROC_ATTR_REQ_SPELLMOD, + PROC_ATTR_USE_STACKS_FOR_CHARGES, + PROC_ATTR_REDUCE_PROC_60, + PROC_ATTR_CANT_PROC_FROM_ITEM_CAST + }; + + for (size_t i = 0; i < sizeof(flags)/sizeof(flags[0]); ++i) + { + for (size_t j = i + 1; j < sizeof(flags)/sizeof(flags[0]); ++j) + { + EXPECT_EQ(flags[i] & flags[j], 0u) + << "Flags at index " << i << " and " << j << " overlap"; + } + } +} diff --git a/src/test/server/game/Spells/SpellProcChanceTest.cpp b/src/test/server/game/Spells/SpellProcChanceTest.cpp new file mode 100644 index 000000000..7c7f6fbbf --- /dev/null +++ b/src/test/server/game/Spells/SpellProcChanceTest.cpp @@ -0,0 +1,317 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcChanceTest.cpp + * @brief Unit tests for proc chance calculations + * + * Tests CalcProcChance() behavior including: + * - Base chance from SpellProcEntry + * - PPM override when DamageInfo is present + * - Chance modifiers (SPELLMOD_CHANCE_OF_SUCCESS) + * - Level 60+ reduction (PROC_ATTR_REDUCE_PROC_60) + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +// ============================================================================= +// Base Chance Tests +// ============================================================================= + +class SpellProcChanceTest : public ::testing::Test +{ +protected: + void SetUp() override {} +}; + +TEST_F(SpellProcChanceTest, BaseChance_UsedWhenNoPPM) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(25.0f) + .WithProcsPerMinute(0.0f) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance(procEntry); + EXPECT_NEAR(result, 25.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, BaseChance_100Percent) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance(procEntry); + EXPECT_NEAR(result, 100.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, BaseChance_Zero) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(0.0f) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance(procEntry); + EXPECT_NEAR(result, 0.0f, 0.01f); +} + +// ============================================================================= +// PPM Override Tests +// ============================================================================= + +TEST_F(SpellProcChanceTest, PPM_OverridesBaseChance_WithDamageInfo) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(50.0f) // This should be ignored + .WithProcsPerMinute(6.0f) + .Build(); + + // With DamageInfo, PPM takes precedence + // 2500ms * 6 PPM / 600 = 25% + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 0.0f, true); + EXPECT_NEAR(result, 25.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, PPM_NotApplied_WithoutDamageInfo) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(50.0f) + .WithProcsPerMinute(6.0f) + .Build(); + + // Without DamageInfo, base chance is used + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 50.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, PPM_WithWeaponSpeedVariation) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .Build(); + + // Fast weapon: 1400ms + float fastResult = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 1400, 0.0f, 0.0f, true); + EXPECT_NEAR(fastResult, 14.0f, 0.01f); + + // Slow weapon: 3300ms + float slowResult = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 3300, 0.0f, 0.0f, true); + EXPECT_NEAR(slowResult, 33.0f, 0.01f); +} + +// ============================================================================= +// Chance Modifier Tests +// ============================================================================= + +TEST_F(SpellProcChanceTest, ChanceModifier_PositiveModifier) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(20.0f) + .Build(); + + // +10% modifier + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 10.0f, 0.0f, false); + EXPECT_NEAR(result, 30.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, ChanceModifier_NegativeModifier) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .Build(); + + // -10% modifier + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, -10.0f, 0.0f, false); + EXPECT_NEAR(result, 20.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, ChanceModifier_AppliedAfterPPM) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .Build(); + + // PPM gives 25%, +5% modifier = 30% + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 5.0f, 0.0f, true); + EXPECT_NEAR(result, 30.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, PPMModifier_IncreasesEffectivePPM) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .Build(); + + // 6 PPM + 2 PPM modifier = 8 effective PPM + // 2500 * 8 / 600 = 33.33% + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 2.0f, true); + EXPECT_NEAR(result, 33.33f, 0.01f); +} + +// ============================================================================= +// Level 60+ Reduction Tests (PROC_ATTR_REDUCE_PROC_60) +// ============================================================================= + +TEST_F(SpellProcChanceTest, Level60Reduction_NoReductionAtLevel60) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 60, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 30.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_NoReductionBelowLevel60) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 50, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 30.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_ReductionAtLevel70) +{ + // Level 70 = 10 levels above 60 + // Reduction = 10/30 = 33.33% + // 30% * (1 - 0.333) = 20% + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 70, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 20.0f, 0.5f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_ReductionAtLevel80) +{ + // Level 80 = 20 levels above 60 + // Reduction = 20/30 = 66.67% + // 30% * (1 - 0.667) = 10% + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 10.0f, 0.5f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_MinimumAtLevel90) +{ + // Level 90 = 30 levels above 60 + // Reduction = 30/30 = 100% + // 30% * (1 - 1.0) = 0% + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 90, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 0.0f, 0.1f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_NotAppliedWithoutAttribute) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(0) // No PROC_ATTR_REDUCE_PROC_60 + .Build(); + + // At level 80, without the attribute, no reduction should occur + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 0.0f, false); + EXPECT_NEAR(result, 30.0f, 0.01f); +} + +TEST_F(SpellProcChanceTest, Level60Reduction_AppliedAfterPPM) +{ + // PPM calculation gives 25%, then level reduction applied + // Level 80 = 20 levels above 60, reduction = 66.67% + // 25% * (1 - 0.667) = 8.33% + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 80, 2500, 0.0f, 0.0f, true); + EXPECT_NEAR(result, 8.33f, 0.5f); +} + +// ============================================================================= +// Helper Function Tests +// ============================================================================= + +TEST_F(SpellProcChanceTest, ApplyLevel60Reduction_DirectTest) +{ + // Level 60: no reduction + EXPECT_NEAR(ProcChanceTestHelper::ApplyLevel60Reduction(30.0f, 60), 30.0f, 0.01f); + + // Level 70: 33.33% reduction + EXPECT_NEAR(ProcChanceTestHelper::ApplyLevel60Reduction(30.0f, 70), 20.0f, 0.5f); + + // Level 80: 66.67% reduction + EXPECT_NEAR(ProcChanceTestHelper::ApplyLevel60Reduction(30.0f, 80), 10.0f, 0.5f); + + // Level 90: 100% reduction + EXPECT_NEAR(ProcChanceTestHelper::ApplyLevel60Reduction(30.0f, 90), 0.0f, 0.1f); + + // Level 100: capped at 0% (no negative chance) + EXPECT_GE(ProcChanceTestHelper::ApplyLevel60Reduction(30.0f, 100), 0.0f); +} + +// ============================================================================= +// Combined Tests +// ============================================================================= + +TEST_F(SpellProcChanceTest, Combined_PPM_ChanceModifier_LevelReduction) +{ + // PPM: 6 at 2500ms = 25% + // Chance modifier: +5% = 30% + // Level 70 reduction: 30% * (1 - 0.333) = 20% + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + float result = ProcChanceTestHelper::SimulateCalcProcChance( + procEntry, 70, 2500, 5.0f, 0.0f, true); + EXPECT_NEAR(result, 20.0f, 1.0f); +} diff --git a/src/test/server/game/Spells/SpellProcChargeTest.cpp b/src/test/server/game/Spells/SpellProcChargeTest.cpp new file mode 100644 index 000000000..ec859862d --- /dev/null +++ b/src/test/server/game/Spells/SpellProcChargeTest.cpp @@ -0,0 +1,409 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcChargeTest.cpp + * @brief Unit tests for proc charge and stack consumption + * + * Tests ConsumeProcCharges() behavior including: + * - Charge decrement on proc + * - Aura removal when charges exhausted + * - PROC_ATTR_USE_STACKS_FOR_CHARGES stack decrement + * - Multiple charge consumption scenarios + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "AuraStub.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcChargeTest : public ::testing::Test +{ +protected: + void SetUp() override {} +}; + +// ============================================================================= +// Basic Charge Consumption Tests +// ============================================================================= + +TEST_F(SpellProcChargeTest, ChargeDecrement_SingleCharge) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(1) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // Consume the single charge + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + EXPECT_EQ(aura->GetCharges(), 0); + EXPECT_TRUE(removed); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, ChargeDecrement_MultipleCharges) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First consumption + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 4); + EXPECT_FALSE(removed); + EXPECT_FALSE(aura->IsRemoved()); + + // Second consumption + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 3); + EXPECT_FALSE(removed); + + // Third consumption + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 2); + EXPECT_FALSE(removed); + + // Fourth consumption + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 1); + EXPECT_FALSE(removed); + + // Final consumption - should remove aura + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 0); + EXPECT_TRUE(removed); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, NoCharges_NoConsumption) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(0) + .Build(); + + aura->SetUsingCharges(false); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + EXPECT_EQ(aura->GetCharges(), 0); + EXPECT_FALSE(removed); + EXPECT_FALSE(aura->IsRemoved()); +} + +// ============================================================================= +// PROC_ATTR_USE_STACKS_FOR_CHARGES Tests +// ============================================================================= + +TEST_F(SpellProcChargeTest, UseStacksForCharges_SingleStack) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithStackAmount(1) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + EXPECT_EQ(aura->GetStackAmount(), 0); + EXPECT_TRUE(removed); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, UseStacksForCharges_MultipleStacks) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // First consumption - 5 -> 4 + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetStackAmount(), 4); + EXPECT_FALSE(removed); + EXPECT_FALSE(aura->IsRemoved()); + + // Second consumption - 4 -> 3 + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetStackAmount(), 3); + EXPECT_FALSE(removed); + + // Consume remaining stacks + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); // 3 -> 2 + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); // 2 -> 1 + + // Final consumption - should remove aura + removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetStackAmount(), 0); + EXPECT_TRUE(removed); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, UseStacksForCharges_IgnoresCharges) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(10) // Has charges + .WithStackAmount(2) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // Should decrement stacks, not charges + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + + EXPECT_EQ(aura->GetStackAmount(), 1); + EXPECT_EQ(aura->GetCharges(), 10); // Charges unchanged + EXPECT_FALSE(removed); +} + +// ============================================================================= +// Real Spell Scenario Tests +// ============================================================================= + +TEST_F(SpellProcChargeTest, Scenario_HotStreak_3Charges) +{ + // Hot Streak (Fire Mage) - 3 charges, consumed on each instant Pyroblast + auto aura = AuraStubBuilder() + .WithId(48108) // Hot Streak + .WithCharges(3) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First Pyroblast + EXPECT_FALSE(ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry)); + EXPECT_EQ(aura->GetCharges(), 2); + + // Second Pyroblast + EXPECT_FALSE(ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry)); + EXPECT_EQ(aura->GetCharges(), 1); + + // Third Pyroblast - aura removed + EXPECT_TRUE(ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry)); + EXPECT_EQ(aura->GetCharges(), 0); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, Scenario_BladeBarrier_5Stacks) +{ + // Blade Barrier (Death Knight) - 5 stacks, consumed over time + auto aura = AuraStubBuilder() + .WithId(55226) // Blade Barrier + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // Simulate stacks being consumed + for (int i = 5; i > 1; --i) + { + EXPECT_EQ(aura->GetStackAmount(), i); + EXPECT_FALSE(ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry)); + } + + // Last stack removal + EXPECT_EQ(aura->GetStackAmount(), 1); + EXPECT_TRUE(ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry)); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, Scenario_Maelstrom_5Stacks) +{ + // Maelstrom Weapon (Enhancement Shaman) - 5 stacks + auto aura = AuraStubBuilder() + .WithId(53817) // Maelstrom Weapon + .WithStackAmount(5) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // At 5 stacks, cast instant Lightning Bolt consumes all stacks + // Simulate consuming all 5 stacks at once + for (int i = 0; i < 5; ++i) + { + ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + } + + EXPECT_EQ(aura->GetStackAmount(), 0); + EXPECT_TRUE(aura->IsRemoved()); +} + +// ============================================================================= +// Edge Case Tests +// ============================================================================= + +TEST_F(SpellProcChargeTest, NullAura_SafeHandling) +{ + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // Should not crash + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(nullptr, procEntry); + EXPECT_FALSE(removed); +} + +TEST_F(SpellProcChargeTest, ZeroStacks_WithUseStacksAttribute) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithStackAmount(0) + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // Should handle gracefully and remove aura + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_TRUE(removed); + EXPECT_TRUE(aura->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, HighChargeCount) +{ + auto aura = AuraStubBuilder() + .WithId(12345) + .WithCharges(255) // Max uint8 + .Build(); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // Consume one charge from max + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), procEntry); + EXPECT_EQ(aura->GetCharges(), 254); + EXPECT_FALSE(removed); +} + +// ============================================================================= +// ProcTestScenario Integration Tests +// ============================================================================= + +TEST_F(SpellProcChargeTest, ProcTestScenario_ChargeConsumption) +{ + ProcTestScenario scenario; + scenario.WithAura(12345, 3); // 3 charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First proc - consumes charge + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 2); + + // Second proc - consumes charge + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 1); + + // Third proc - consumes last charge and removes aura + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 0); + EXPECT_TRUE(scenario.GetAura()->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, ProcTestScenario_StackConsumption) +{ + ProcTestScenario scenario; + scenario.WithAura(12345, 0, 3); // 3 stacks + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // First proc - consumes stack + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetStackAmount(), 2); + + // Second proc - consumes stack + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetStackAmount(), 1); + + // Third proc - consumes last stack and removes aura + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetStackAmount(), 0); + EXPECT_TRUE(scenario.GetAura()->IsRemoved()); +} + +TEST_F(SpellProcChargeTest, ProcTestScenario_ChargesWithCooldown) +{ + using namespace std::chrono_literals; + + ProcTestScenario scenario; + scenario.WithAura(12345, 3); // 3 charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(1000ms) // 1 second cooldown + .Build(); + + // First proc at t=0 - should work + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 2); + + // Immediate second proc - blocked by cooldown + EXPECT_FALSE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 2); // No charge consumed + + // Wait for cooldown + scenario.AdvanceTime(1100ms); + + // Third proc - should work + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + EXPECT_EQ(scenario.GetAura()->GetCharges(), 1); +} diff --git a/src/test/server/game/Spells/SpellProcConditionsTest.cpp b/src/test/server/game/Spells/SpellProcConditionsTest.cpp new file mode 100644 index 000000000..eb57b2a90 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcConditionsTest.cpp @@ -0,0 +1,386 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcConditionsTest.cpp + * @brief Unit tests for conditions system integration in proc system + * + * Tests the logic from SpellAuras.cpp:2232-2236: + * - CONDITION_SOURCE_TYPE_SPELL_PROC (24) lookup + * - Condition met allows proc + * - Condition not met blocks proc + * - Empty conditions allow proc + * - Multiple conditions (AND logic within ElseGroup) + * - ElseGroup OR logic + * + * ============================================================================ + * TEST DESIGN: Configuration-Based Testing + * ============================================================================ + * + * These tests use ConditionsConfig structs to simulate the result of + * condition evaluation without requiring actual ConditionMgr queries. + * Each test configures: + * - sourceType: The condition source type (24 = CONDITION_SOURCE_TYPE_SPELL_PROC) + * - hasConditions: Whether any conditions are registered for this spell + * - conditionsMet: The result of ConditionMgr::IsObjectMeetToConditions() + * + * The actual condition types (CONDITION_AURA, CONDITION_HP_PCT, etc.) are + * not evaluated here - we test the proc system's response to condition + * evaluation results. Individual condition types are tested in the + * conditions system unit tests. + * + * No GTEST_SKIP() is used in this file - all tests run with their configured + * scenarios, testing both positive and negative cases explicitly. + * ============================================================================ + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcConditionsTest : public ::testing::Test +{ +protected: + void SetUp() override {} +}; + +// ============================================================================= +// Basic Condition Tests +// ============================================================================= + +TEST_F(SpellProcConditionsTest, NoConditions_AllowsProc) +{ + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = false; // No conditions registered + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "No conditions should allow proc"; +} + +TEST_F(SpellProcConditionsTest, ConditionsMet_AllowsProc) +{ + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Conditions met should allow proc"; +} + +TEST_F(SpellProcConditionsTest, ConditionsNotMet_BlocksProc) +{ + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Conditions not met should block proc"; +} + +// ============================================================================= +// Source Type Tests - CONDITION_SOURCE_TYPE_SPELL_PROC = 24 +// ============================================================================= + +TEST_F(SpellProcConditionsTest, SourceType_SpellProc) +{ + ProcChanceTestHelper::ConditionsConfig config; + config.sourceType = 24; // CONDITION_SOURCE_TYPE_SPELL_PROC + config.hasConditions = true; + config.conditionsMet = true; + + EXPECT_EQ(config.sourceType, 24u) + << "Source type should be CONDITION_SOURCE_TYPE_SPELL_PROC (24)"; + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +// ============================================================================= +// Multiple Conditions Scenarios (AND Logic) +// ============================================================================= + +TEST_F(SpellProcConditionsTest, MultipleConditions_AllMet_AllowsProc) +{ + // Simulating multiple conditions in same ElseGroup (AND) + // In reality, ConditionMgr evaluates all - we just test the result + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; // All conditions passed + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "All conditions met (AND) should allow proc"; +} + +TEST_F(SpellProcConditionsTest, MultipleConditions_OneFails_BlocksProc) +{ + // One condition in the group fails + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; // At least one condition failed + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "One failed condition (AND) should block proc"; +} + +// ============================================================================= +// ElseGroup OR Logic Scenarios +// ============================================================================= + +TEST_F(SpellProcConditionsTest, ElseGroup_OneGroupPasses_AllowsProc) +{ + // ElseGroup logic: any group passing means conditions met + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; // At least one group passed + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "At least one ElseGroup passing should allow proc"; +} + +TEST_F(SpellProcConditionsTest, ElseGroup_AllGroupsFail_BlocksProc) +{ + // All ElseGroups fail + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; // No groups passed + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "All ElseGroups failing should block proc"; +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcConditionsTest, Scenario_ProcOnlyInCombat) +{ + // Condition: Player must be in combat + ProcChanceTestHelper::ConditionsConfig inCombat; + inCombat.hasConditions = true; + inCombat.conditionsMet = true; // In combat + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(inCombat)) + << "Proc should work when in combat"; + + ProcChanceTestHelper::ConditionsConfig outOfCombat; + outOfCombat.hasConditions = true; + outOfCombat.conditionsMet = false; // Out of combat + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(outOfCombat)) + << "Proc should be blocked when out of combat"; +} + +TEST_F(SpellProcConditionsTest, Scenario_ProcOnlyVsUndead) +{ + // Condition: Target must be undead creature type + ProcChanceTestHelper::ConditionsConfig vsUndead; + vsUndead.hasConditions = true; + vsUndead.conditionsMet = true; // Target is undead + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(vsUndead)) + << "Proc should work against undead"; + + ProcChanceTestHelper::ConditionsConfig vsHumanoid; + vsHumanoid.hasConditions = true; + vsHumanoid.conditionsMet = false; // Target is humanoid + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(vsHumanoid)) + << "Proc should be blocked against non-undead"; +} + +TEST_F(SpellProcConditionsTest, Scenario_ProcRequiresAura) +{ + // Condition: Actor must have specific aura + ProcChanceTestHelper::ConditionsConfig hasAura; + hasAura.hasConditions = true; + hasAura.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(hasAura)) + << "Proc should work when required aura is present"; + + ProcChanceTestHelper::ConditionsConfig noAura; + noAura.hasConditions = true; + noAura.conditionsMet = false; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(noAura)) + << "Proc should be blocked when required aura is missing"; +} + +TEST_F(SpellProcConditionsTest, Scenario_ProcRequiresHealthBelow) +{ + // Condition: Actor health must be below threshold + ProcChanceTestHelper::ConditionsConfig lowHealth; + lowHealth.hasConditions = true; + lowHealth.conditionsMet = true; // Health below 35% + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(lowHealth)) + << "Proc should work when health is below threshold"; + + ProcChanceTestHelper::ConditionsConfig highHealth; + highHealth.hasConditions = true; + highHealth.conditionsMet = false; // Health above 35% + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(highHealth)) + << "Proc should be blocked when health is above threshold"; +} + +TEST_F(SpellProcConditionsTest, Scenario_ProcInAreaOnly) +{ + // Condition: Must be in specific zone/area + ProcChanceTestHelper::ConditionsConfig inArea; + inArea.hasConditions = true; + inArea.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(inArea)) + << "Proc should work when in required area"; + + ProcChanceTestHelper::ConditionsConfig notInArea; + notInArea.hasConditions = true; + notInArea.conditionsMet = false; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(notInArea)) + << "Proc should be blocked when not in required area"; +} + +// ============================================================================= +// Condition Type Scenarios (Common CONDITION_* types used with procs) +// ============================================================================= + +TEST_F(SpellProcConditionsTest, ConditionType_Aura) +{ + // CONDITION_AURA = 1 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +TEST_F(SpellProcConditionsTest, ConditionType_Item) +{ + // CONDITION_ITEM = 2 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +TEST_F(SpellProcConditionsTest, ConditionType_ItemEquipped) +{ + // CONDITION_ITEM_EQUIPPED = 3 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; // Required item not equipped + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Proc blocked when required item not equipped"; +} + +TEST_F(SpellProcConditionsTest, ConditionType_QuestRewarded) +{ + // CONDITION_QUESTREWARDED = 8 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; // Required quest completed + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Proc allowed when quest completed"; +} + +TEST_F(SpellProcConditionsTest, ConditionType_CreatureType) +{ + // CONDITION_CREATURE_TYPE = 18 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; // Wrong creature type + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Proc blocked when creature type doesn't match"; +} + +TEST_F(SpellProcConditionsTest, ConditionType_HPVal) +{ + // CONDITION_HP_VAL = 23 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; // HP threshold met + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +TEST_F(SpellProcConditionsTest, ConditionType_HPPct) +{ + // CONDITION_HP_PCT = 25 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = false; // HP percent threshold not met + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +TEST_F(SpellProcConditionsTest, ConditionType_InCombat) +{ + // CONDITION_IN_COMBAT = 36 + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; // In combat + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcConditionsTest, EdgeCase_EmptyConditionList) +{ + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = false; + config.conditionsMet = false; // Doesn't matter when no conditions + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)) + << "Empty condition list should allow proc"; +} + +TEST_F(SpellProcConditionsTest, EdgeCase_ConditionsButAlwaysTrue) +{ + // Conditions exist but are always satisfied (e.g., always-true condition) + ProcChanceTestHelper::ConditionsConfig config; + config.hasConditions = true; + config.conditionsMet = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(config)); +} + +TEST_F(SpellProcConditionsTest, EdgeCase_MultipleSourceTypes) +{ + // Different source types shouldn't interfere + // Each spell proc has its own conditions by spell ID + ProcChanceTestHelper::ConditionsConfig spell1; + spell1.sourceType = 24; + spell1.hasConditions = true; + spell1.conditionsMet = true; + + ProcChanceTestHelper::ConditionsConfig spell2; + spell2.sourceType = 24; + spell2.hasConditions = true; + spell2.conditionsMet = false; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToConditions(spell1)); + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToConditions(spell2)); +} diff --git a/src/test/server/game/Spells/SpellProcCooldownTest.cpp b/src/test/server/game/Spells/SpellProcCooldownTest.cpp new file mode 100644 index 000000000..ed997a629 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcCooldownTest.cpp @@ -0,0 +1,219 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcCooldownTest.cpp + * @brief Unit tests for proc internal cooldown system + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "AuraStub.h" +#include "gtest/gtest.h" + +using namespace testing; +using namespace std::chrono_literals; + +class SpellProcCooldownTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _now = std::chrono::steady_clock::now(); + } + + std::chrono::steady_clock::time_point _now; +}; + +// ============================================================================= +// Basic Cooldown Tests +// ============================================================================= + +TEST_F(SpellProcCooldownTest, NotOnCooldown_Initially) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now)); +} + +TEST_F(SpellProcCooldownTest, OnCooldown_AfterProc) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + // Apply 1 second cooldown + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 1000); + + // Should be on cooldown immediately after + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now)); + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 500ms)); +} + +TEST_F(SpellProcCooldownTest, NotOnCooldown_AfterExpiry) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + // Apply 1 second cooldown + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 1000); + + // Should not be on cooldown after 1 second + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 1001ms)); +} + +TEST_F(SpellProcCooldownTest, ExactCooldownBoundary) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 1000); + + // At exactly cooldown time, should still be on cooldown (< not <=) + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 999ms)); + // One millisecond after should be off cooldown + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 1000ms)); +} + +// ============================================================================= +// Zero Cooldown Tests +// ============================================================================= + +TEST_F(SpellProcCooldownTest, ZeroCooldown_NeverBlocks) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + // Zero cooldown should not apply any cooldown + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 0); + + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now)); +} + +// ============================================================================= +// Cooldown Reset Tests +// ============================================================================= + +TEST_F(SpellProcCooldownTest, CooldownCanBeReset) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 5000); + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now)); + + aura->ResetProcCooldown(); + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now)); +} + +TEST_F(SpellProcCooldownTest, CooldownCanBeExtended) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + + // Apply 1 second cooldown + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 1000); + + // Extend to 5 seconds + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 5000); + + // Should still be on cooldown after 2 seconds + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 2000ms)); +} + +// ============================================================================= +// Scenario Tests +// ============================================================================= + +TEST_F(SpellProcCooldownTest, Scenario_LeaderOfThePack_6SecCooldown) +{ + // Leader of the Pack has a 6 second internal cooldown + auto aura = AuraStubBuilder().WithId(24932).Build(); + + // First proc + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 6000); + + // Blocked at 3 seconds + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 3000ms)); + + // Blocked at 5.9 seconds + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 5999ms)); + + // Allowed at 6 seconds + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 6000ms)); +} + +TEST_F(SpellProcCooldownTest, Scenario_WanderingPlague_1SecCooldown) +{ + // Wandering Plague has a 1 second internal cooldown + auto aura = AuraStubBuilder().WithId(49217).Build(); + + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), _now, 1000); + + // Blocked at 0.5 seconds + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 500ms)); + + // Allowed at 1 second + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), _now + 1000ms)); +} + +TEST_F(SpellProcCooldownTest, Scenario_MultipleProcsWithCooldown) +{ + auto aura = AuraStubBuilder().WithId(12345).Build(); + auto time = _now; + + // First proc at t=0 + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), time)); + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), time, 1000); + + // Second attempt at t=0.5 (blocked) + time += 500ms; + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), time)); + + // Third attempt at t=1.0 (allowed) + time += 500ms; + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), time)); + ProcChanceTestHelper::ApplyProcCooldown(aura.get(), time, 1000); + + // Fourth attempt at t=1.5 (blocked) + time += 500ms; + EXPECT_TRUE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), time)); + + // Fifth attempt at t=2.0 (allowed) + time += 500ms; + EXPECT_FALSE(ProcChanceTestHelper::IsProcOnCooldown(aura.get(), time)); +} + +// ============================================================================= +// ProcTestScenario Tests +// ============================================================================= + +TEST_F(SpellProcCooldownTest, ProcTestScenario_CooldownBlocking) +{ + ProcTestScenario scenario; + scenario.WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(1000ms) + .Build(); + + // First proc should succeed + EXPECT_TRUE(scenario.SimulateProc(procEntry)); + + // Second proc immediately after should fail (on cooldown) + EXPECT_FALSE(scenario.SimulateProc(procEntry)); + + // Advance time past cooldown + scenario.AdvanceTime(1100ms); + + // Third proc should succeed + EXPECT_TRUE(scenario.SimulateProc(procEntry)); +} diff --git a/src/test/server/game/Spells/SpellProcDBCValidationTest.cpp b/src/test/server/game/Spells/SpellProcDBCValidationTest.cpp new file mode 100644 index 000000000..99eae3673 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcDBCValidationTest.cpp @@ -0,0 +1,369 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcDBCValidationTest.cpp + * @brief Unit tests for validating spell_proc entries against Spell.dbc + * + * Tests validate that spell_proc entries provide value beyond DBC defaults: + * - Entries that override DBC ProcFlags/ProcChance/ProcCharges + * - Entries that add new functionality (PPM, cooldowns, filtering) + * - Identification of potentially redundant entries + * + * ============================================================================ + * DBC DATA POPULATION STATUS + * ============================================================================ + * + * The DBC_ProcFlags, DBC_ProcChance, and DBC_ProcCharges fields in + * SpellProcTestEntry are currently populated with zeros (0, 0, 0) for all + * entries. To fully validate spell_proc entries against DBC: + * + * 1. Use the generate_spell_proc_dbc_data.py script with MCP connection + * 2. Or manually query: get_spell_dbc_proc_info(spell_id) for each spell + * + * Tests that require DBC data will check HasDBCData() and skip appropriately. + * Once DBC data is populated, the statistics tests will show: + * - How many entries override DBC defaults + * - How many entries add new functionality not in DBC + * - How many entries might be redundant (just duplicate DBC values) + * ============================================================================ + */ + +#include "SpellProcTestData.h" +#include "gtest/gtest.h" +#include +#include +#include + +using namespace testing; + +// ============================================================================= +// DBC Validation Test Fixture +// ============================================================================= + +class SpellProcDBCValidationTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _allEntries = GetAllSpellProcTestEntries(); + } + + std::vector _allEntries; +}; + +// ============================================================================= +// Parameterized Tests for All Entries +// ============================================================================= + +class SpellProcDBCValidationParamTest : public ::testing::TestWithParam +{ +}; + +TEST_P(SpellProcDBCValidationParamTest, EntryHasValidSpellId) +{ + auto const& entry = GetParam(); + int32_t spellId = std::abs(entry.SpellId); + + // Spell ID must be positive after abs + EXPECT_GT(spellId, 0) << "SpellId must be non-zero"; + + // Spell IDs in WotLK should be < 80000 + EXPECT_LT(spellId, 80000u) + << "SpellId " << spellId << " seems out of range for WotLK"; +} + +INSTANTIATE_TEST_SUITE_P( + AllSpellProcEntries, + SpellProcDBCValidationParamTest, + ::testing::ValuesIn(GetAllSpellProcTestEntries()), + [](const testing::TestParamInfo& info) { + // Create unique test name from SpellId (handle negative IDs) + int32_t id = info.param.SpellId; + if (id < 0) + return "SpellId_N" + std::to_string(-id); + return "SpellId_" + std::to_string(id); + } +); + +// ============================================================================= +// Statistics Tests +// ============================================================================= + +TEST_F(SpellProcDBCValidationTest, CountEntriesWithDBCData) +{ + size_t withDBC = 0; + size_t withoutDBC = 0; + + for (auto const& entry : _allEntries) + { + if (entry.HasDBCData()) + withDBC++; + else + withoutDBC++; + } + + std::cout << "[ INFO ] Entries with DBC data: " << withDBC << "\n"; + std::cout << "[ INFO ] Entries without DBC data: " << withoutDBC << std::endl; + + // All entries should eventually have DBC data + // For now, just verify the count + EXPECT_EQ(_allEntries.size(), 869u); +} + +TEST_F(SpellProcDBCValidationTest, CountEntriesAddingValue) +{ + size_t addsValue = 0; + size_t potentiallyRedundant = 0; + size_t noDBCYet = 0; + + for (auto const& entry : _allEntries) + { + // SKIP REASON: Entries without DBC data populated cannot be compared + // against DBC defaults. The HasDBCData() check returns false when + // DBC_ProcFlags, DBC_ProcChance, and DBC_ProcCharges are all zero. + // Once DBC data is populated via MCP tools, this count should be 0. + if (!entry.HasDBCData()) + { + noDBCYet++; + continue; + } + + if (entry.AddsValueBeyondDBC()) + addsValue++; + else + potentiallyRedundant++; + } + + std::cout << "[ INFO ] Entries adding value: " << addsValue << "\n"; + std::cout << "[ INFO ] Potentially redundant: " << potentiallyRedundant << "\n"; + std::cout << "[ INFO ] DBC data not yet populated: " << noDBCYet << std::endl; + + // Most entries should add value (have PPM, cooldowns, filtering, etc.) + if (addsValue + potentiallyRedundant > 0) + { + float valueRate = static_cast(addsValue) / (addsValue + potentiallyRedundant) * 100; + std::cout << "[ INFO ] Value-add rate: " << valueRate << "%" << std::endl; + } +} + +TEST_F(SpellProcDBCValidationTest, CategorizeEntriesByFeature) +{ + size_t hasPPM = 0; + size_t hasCooldown = 0; + size_t hasSpellTypeMask = 0; + size_t hasSpellPhaseMask = 0; + size_t hasHitMask = 0; + size_t hasAttributesMask = 0; + size_t hasSpellFamilyMask = 0; + size_t hasSchoolMask = 0; + size_t hasCharges = 0; + size_t hasDisableEffectsMask = 0; + + for (auto const& entry : _allEntries) + { + if (entry.ProcsPerMinute > 0) hasPPM++; + if (entry.Cooldown > 0) hasCooldown++; + if (entry.SpellTypeMask != 0) hasSpellTypeMask++; + if (entry.SpellPhaseMask != 0) hasSpellPhaseMask++; + if (entry.HitMask != 0) hasHitMask++; + if (entry.AttributesMask != 0) hasAttributesMask++; + if (entry.SpellFamilyMask0 != 0 || entry.SpellFamilyMask1 != 0 || entry.SpellFamilyMask2 != 0) + hasSpellFamilyMask++; + if (entry.SchoolMask != 0) hasSchoolMask++; + if (entry.Charges > 0) hasCharges++; + if (entry.DisableEffectsMask != 0) hasDisableEffectsMask++; + } + + std::cout << "[ INFO ] Feature usage (adds value beyond DBC):\n" + << " PPM: " << hasPPM << "\n" + << " Cooldown: " << hasCooldown << "\n" + << " SpellTypeMask: " << hasSpellTypeMask << "\n" + << " SpellPhaseMask: " << hasSpellPhaseMask << "\n" + << " HitMask: " << hasHitMask << "\n" + << " AttributesMask: " << hasAttributesMask << "\n" + << " SpellFamilyMask: " << hasSpellFamilyMask << "\n" + << " SchoolMask: " << hasSchoolMask << "\n" + << " Charges: " << hasCharges << "\n" + << " DisableEffectsMask: " << hasDisableEffectsMask << std::endl; + + // Most entries should use at least one extended feature + size_t usingExtendedFeatures = 0; + for (auto const& entry : _allEntries) + { + if (entry.ProcsPerMinute > 0 || entry.Cooldown > 0 || + entry.SpellTypeMask != 0 || entry.SpellPhaseMask != 0 || + entry.HitMask != 0 || entry.AttributesMask != 0 || + entry.SpellFamilyMask0 != 0 || entry.SpellFamilyMask1 != 0 || + entry.SpellFamilyMask2 != 0 || entry.SchoolMask != 0 || + entry.DisableEffectsMask != 0) + { + usingExtendedFeatures++; + } + } + + std::cout << "[ INFO ] Entries using extended features: " << usingExtendedFeatures + << " / " << _allEntries.size() << std::endl; + + // At least 80% should use extended features + EXPECT_GT(usingExtendedFeatures, _allEntries.size() * 80 / 100) + << "Most entries should use extended features"; +} + +TEST_F(SpellProcDBCValidationTest, IdentifyDBCOverrides) +{ + size_t overridesProcFlags = 0; + size_t overridesChance = 0; + size_t overridesCharges = 0; + + for (auto const& entry : _allEntries) + { + // SKIP REASON: Cannot compare against DBC defaults when DBC data + // is not populated. All 869 entries currently have DBC fields = 0. + // Once populated, this loop will count actual DBC overrides. + if (!entry.HasDBCData()) + continue; + + if (entry.ProcFlags != 0 && entry.ProcFlags != entry.DBC_ProcFlags) + overridesProcFlags++; + + if (entry.Chance != 0 && static_cast(entry.Chance) != entry.DBC_ProcChance) + overridesChance++; + + if (entry.Charges != 0 && entry.Charges != entry.DBC_ProcCharges) + overridesCharges++; + } + + std::cout << "[ INFO ] DBC Overrides:\n" + << " ProcFlags: " << overridesProcFlags << "\n" + << " Chance: " << overridesChance << "\n" + << " Charges: " << overridesCharges << std::endl; +} + +// ============================================================================= +// Negative Spell ID Tests (Effect-specific procs) +// ============================================================================= + +TEST_F(SpellProcDBCValidationTest, CountNegativeSpellIds) +{ + size_t negativeIds = 0; + size_t positiveIds = 0; + + for (auto const& entry : _allEntries) + { + if (entry.SpellId < 0) + negativeIds++; + else + positiveIds++; + } + + std::cout << "[ INFO ] Negative SpellIds (effect-specific): " << negativeIds << "\n"; + std::cout << "[ INFO ] Positive SpellIds: " << positiveIds << std::endl; + + // Both types should exist + EXPECT_GT(negativeIds, 0u) << "Should have some effect-specific (negative ID) entries"; + EXPECT_GT(positiveIds, 0u) << "Should have some spell-wide (positive ID) entries"; +} + +// ============================================================================= +// SpellFamily Coverage Tests +// ============================================================================= + +TEST_F(SpellProcDBCValidationTest, CoverageBySpellFamily) +{ + std::map familyCounts; + std::map familyNames = { + {0, "Generic"}, {3, "Mage"}, {4, "Warrior"}, {5, "Warlock"}, + {6, "Priest"}, {7, "Druid"}, {8, "Rogue"}, {9, "Hunter"}, + {10, "Paladin"}, {11, "Shaman"}, {15, "DeathKnight"} + }; + + for (auto const& entry : _allEntries) + { + familyCounts[entry.SpellFamilyName]++; + } + + std::cout << "[ INFO ] Entries by SpellFamily:\n"; + for (auto const& [family, count] : familyCounts) + { + std::string name = familyNames.count(family) ? familyNames[family] : "Unknown"; + std::cout << " " << name << " (" << family << "): " << count << "\n"; + } + + // Should have entries from multiple spell families + EXPECT_GT(familyCounts.size(), 5u) << "Should cover multiple spell families"; +} + +// ============================================================================= +// Data Integrity Tests +// ============================================================================= + +TEST_F(SpellProcDBCValidationTest, NoDuplicateSpellIds) +{ + std::set seenIds; + std::vector duplicates; + + for (auto const& entry : _allEntries) + { + if (seenIds.count(entry.SpellId)) + duplicates.push_back(entry.SpellId); + else + seenIds.insert(entry.SpellId); + } + + if (!duplicates.empty()) + { + std::cout << "[ WARN ] Duplicate SpellIds found: "; + for (auto id : duplicates) + std::cout << id << " "; + std::cout << std::endl; + } + + EXPECT_TRUE(duplicates.empty()) << "Should have no duplicate SpellIds"; +} + +TEST_F(SpellProcDBCValidationTest, AllEntriesHaveValidStructure) +{ + for (auto const& entry : _allEntries) + { + // SpellId must be non-zero + EXPECT_NE(entry.SpellId, 0) + << "SpellId cannot be zero"; + + // If Chance is set, it should be reasonable (0-100, or 101 for 100% from DBC) + if (entry.Chance > 0) + { + EXPECT_LE(entry.Chance, 101.0f) + << "SpellId " << entry.SpellId << " has unusual Chance: " << entry.Chance; + } + + // PPM should be reasonable (typically 0-60) + if (entry.ProcsPerMinute > 0) + { + EXPECT_LE(entry.ProcsPerMinute, 60.0f) + << "SpellId " << entry.SpellId << " has unusual PPM: " << entry.ProcsPerMinute; + } + + // SpellPhaseMask should use valid values + if (entry.SpellPhaseMask != 0) + { + // Valid phase masks: PROC_SPELL_PHASE_CAST(1), PROC_SPELL_PHASE_HIT(2), PROC_SPELL_PHASE_FINISH(4) + EXPECT_LE(entry.SpellPhaseMask, 7u) + << "SpellId " << entry.SpellId << " has unusual SpellPhaseMask: " << entry.SpellPhaseMask; + } + } +} diff --git a/src/test/server/game/Spells/SpellProcDataDrivenTest.cpp b/src/test/server/game/Spells/SpellProcDataDrivenTest.cpp new file mode 100644 index 000000000..56a9e4437 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcDataDrivenTest.cpp @@ -0,0 +1,756 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcDataDrivenTest.cpp + * @brief Comprehensive data-driven tests for ALL 869 spell_proc entries + * + * This file auto-tests every spell_proc entry from the database. + * Data is generated by: src/test/scripts/generate_spell_proc_data.py + */ + +#include "ProcEventInfoHelper.h" +#include "SpellInfoTestHelper.h" +#include "SpellMgr.h" +#include "SpellProcTestData.h" +#include "WorldMock.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include +#include + +using namespace testing; + +// ============================================================================= +// Proc Flag Mappings +// ============================================================================= + +struct ProcFlagScenario +{ + uint32 procFlag; + const char* name; + uint32 defaultHitMask; + uint32 defaultSpellTypeMask; + uint32 defaultSpellPhaseMask; + bool requiresSpellPhase; +}; + +static const std::vector PROC_FLAG_SCENARIOS = { + { PROC_FLAG_DONE_MELEE_AUTO_ATTACK, "DoneMeleeAuto", PROC_HIT_NORMAL, 0, 0, false }, + { PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK, "TakenMeleeAuto", PROC_HIT_NORMAL, 0, 0, false }, + { PROC_FLAG_DONE_MAINHAND_ATTACK, "DoneMainhand", PROC_HIT_NORMAL, 0, 0, false }, + { PROC_FLAG_DONE_OFFHAND_ATTACK, "DoneOffhand", PROC_HIT_NORMAL, 0, 0, false }, + { PROC_FLAG_DONE_RANGED_AUTO_ATTACK, "DoneRangedAuto", PROC_HIT_NORMAL, 0, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK, "TakenRangedAuto", PROC_HIT_NORMAL, 0, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS, "DoneSpellMelee", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS, "TakenSpellMelee", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_RANGED_DMG_CLASS, "DoneSpellRanged", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_RANGED_DMG_CLASS, "TakenSpellRanged", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS, "DoneSpellNonePos", PROC_HIT_NORMAL, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS, "TakenSpellNonePos", PROC_HIT_NORMAL, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG, "DoneSpellNoneNeg", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG, "TakenSpellNoneNeg", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS, "DoneSpellMagicPos", PROC_HIT_NORMAL, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS,"TakenSpellMagicPos", PROC_HIT_NORMAL, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG, "DoneSpellMagicNeg", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG,"TakenSpellMagicNeg", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_DONE_PERIODIC, "DonePeriodic", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_PERIODIC, "TakenPeriodic", PROC_HIT_NORMAL, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, true }, + { PROC_FLAG_TAKEN_DAMAGE, "TakenDamage", PROC_HIT_NORMAL, 0, 0, false }, + { PROC_FLAG_KILL, "Kill", 0, 0, 0, false }, + { PROC_FLAG_KILLED, "Killed", 0, 0, 0, false }, + { PROC_FLAG_DEATH, "Death", 0, 0, 0, false }, + { PROC_FLAG_DONE_TRAP_ACTIVATION, "TrapActivation", PROC_HIT_NORMAL, 0, PROC_SPELL_PHASE_HIT, true }, +}; + +static const std::vector> HIT_MASK_SCENARIOS = { + { PROC_HIT_NORMAL, "Normal" }, + { PROC_HIT_CRITICAL, "Critical" }, + { PROC_HIT_MISS, "Miss" }, + { PROC_HIT_DODGE, "Dodge" }, + { PROC_HIT_PARRY, "Parry" }, + { PROC_HIT_BLOCK, "Block" }, + { PROC_HIT_EVADE, "Evade" }, + { PROC_HIT_IMMUNE, "Immune" }, + { PROC_HIT_DEFLECT, "Deflect" }, + { PROC_HIT_ABSORB, "Absorb" }, + { PROC_HIT_REFLECT, "Reflect" }, + { PROC_HIT_INTERRUPT, "Interrupt" }, + { PROC_HIT_FULL_BLOCK, "FullBlock" }, +}; + +// ============================================================================= +// Test Fixture for Comprehensive Database Testing +// ============================================================================= + +class SpellProcDatabaseTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _originalWorld = sWorld.release(); + _worldMock = new NiceMock(); + sWorld.reset(_worldMock); + + static std::string emptyString; + ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); + + // Load all entries from generated data + _allEntries = GetAllSpellProcTestEntries(); + + // Create a default SpellInfo for spell-type procs + _defaultSpellInfo = SpellInfoBuilder() + .WithId(99999) + .WithSpellFamilyName(0) + .Build(); + } + + void TearDown() override + { + IWorld* currentWorld = sWorld.release(); + delete currentWorld; + _worldMock = nullptr; + sWorld.reset(_originalWorld); + + delete _defaultSpellInfo; + _defaultSpellInfo = nullptr; + delete _damageInfo; + _damageInfo = nullptr; + delete _healInfo; + _healInfo = nullptr; + } + + /** + * @brief Find the first matching proc flag scenario for given flags + */ + ProcFlagScenario const* FindMatchingScenario(uint32 procFlags) + { + for (auto const& scenario : PROC_FLAG_SCENARIOS) + { + if (procFlags & scenario.procFlag) + return &scenario; + } + return nullptr; + } + + /** + * @brief Get effective hit mask for an entry + */ + uint32 GetEffectiveHitMask(SpellProcTestEntry const& entry, ProcFlagScenario const* scenario) + { + if (entry.HitMask != 0) + { + // Return first set bit + for (auto const& [mask, name] : HIT_MASK_SCENARIOS) + { + if (entry.HitMask & mask) + return mask; + } + } + return scenario ? scenario->defaultHitMask : PROC_HIT_NORMAL; + } + + /** + * @brief Get effective spell type mask + */ + uint32 GetEffectiveSpellTypeMask(SpellProcTestEntry const& entry, ProcFlagScenario const* scenario) + { + if (entry.SpellTypeMask != 0) + { + if (entry.SpellTypeMask & PROC_SPELL_TYPE_DAMAGE) + return PROC_SPELL_TYPE_DAMAGE; + if (entry.SpellTypeMask & PROC_SPELL_TYPE_HEAL) + return PROC_SPELL_TYPE_HEAL; + if (entry.SpellTypeMask & PROC_SPELL_TYPE_NO_DMG_HEAL) + return PROC_SPELL_TYPE_NO_DMG_HEAL; + } + return scenario ? scenario->defaultSpellTypeMask : PROC_SPELL_TYPE_MASK_ALL; + } + + /** + * @brief Get effective spell phase mask + */ + uint32 GetEffectiveSpellPhaseMask(SpellProcTestEntry const& entry, ProcFlagScenario const* scenario) + { + if (entry.SpellPhaseMask != 0) + return entry.SpellPhaseMask; + if (scenario && scenario->requiresSpellPhase) + return scenario->defaultSpellPhaseMask ? scenario->defaultSpellPhaseMask : PROC_SPELL_PHASE_HIT; + return 0; + } + + /** + * @brief Check if entry requires SpellFamily matching (which we can't test without SpellInfo) + * Any entry with SpellFamilyName > 0 will cause CanSpellTriggerProcOnEvent to access + * eventInfo.GetSpellInfo() which returns null in our test, causing a crash. + */ + bool RequiresSpellFamilyMatch(SpellProcTestEntry const& entry) + { + // Skip any entry with SpellFamilyName set - the code will try to access SpellInfo + return entry.SpellFamilyName != 0; + } + + /** + * @brief Check if the proc flags indicate a spell-type event that needs SpellInfo + */ + bool IsSpellTypeProc(uint32 procFlags) + { + return (procFlags & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION)) != 0; + } + + /** + * @brief Create a ProcEventInfo with proper DamageInfo/HealInfo for spell-type procs + */ + ProcEventInfo CreateEventInfo(uint32 typeMask, uint32 hitMask, uint32 spellTypeMask, uint32 spellPhaseMask) + { + auto builder = ProcEventInfoBuilder() + .WithTypeMask(typeMask) + .WithHitMask(hitMask) + .WithSpellTypeMask(spellTypeMask) + .WithSpellPhaseMask(spellPhaseMask); + + // For spell-type procs, provide DamageInfo or HealInfo with SpellInfo + if (IsSpellTypeProc(typeMask)) + { + if (spellTypeMask & PROC_SPELL_TYPE_HEAL) + { + if (!_healInfo) + _healInfo = new HealInfo(nullptr, nullptr, 100, _defaultSpellInfo, SPELL_SCHOOL_MASK_HOLY); + builder.WithHealInfo(_healInfo); + } + else + { + if (!_damageInfo) + _damageInfo = new DamageInfo(nullptr, nullptr, 100, _defaultSpellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + builder.WithDamageInfo(_damageInfo); + } + } + + return builder.Build(); + } + + IWorld* _originalWorld = nullptr; + NiceMock* _worldMock = nullptr; + SpellInfo* _defaultSpellInfo = nullptr; + DamageInfo* _damageInfo = nullptr; + HealInfo* _healInfo = nullptr; + std::vector _allEntries; +}; + +// ============================================================================= +// Comprehensive Tests for All 869 Entries +// ============================================================================= + +TEST_F(SpellProcDatabaseTest, AllEntriesLoaded) +{ + EXPECT_EQ(_allEntries.size(), 869u) << "Should have all 869 spell_proc entries loaded"; +} + +TEST_F(SpellProcDatabaseTest, AllEntriesWithProcFlags_PositiveTest) +{ + int totalTested = 0; + int passed = 0; + int skippedFamily = 0; + int skippedNoFlags = 0; + + for (auto const& entry : _allEntries) + { + // Skip entries with no ProcFlags (they rely on other conditions) + if (entry.ProcFlags == 0) + { + skippedNoFlags++; + continue; + } + + // Skip entries that require SpellFamily matching + if (RequiresSpellFamilyMatch(entry)) + { + skippedFamily++; + continue; + } + + totalTested++; + + ProcFlagScenario const* scenario = FindMatchingScenario(entry.ProcFlags); + if (!scenario) + continue; + + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = CreateEventInfo( + scenario->procFlag, + GetEffectiveHitMask(entry, scenario), + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + { + passed++; + } + } + + // Report statistics + float passRate = totalTested > 0 ? (float)passed / totalTested * 100 : 0; + SCOPED_TRACE("Total entries: " + std::to_string(_allEntries.size())); + SCOPED_TRACE("Tested: " + std::to_string(totalTested)); + SCOPED_TRACE("Passed: " + std::to_string(passed) + " (" + std::to_string((int)passRate) + "%)"); + SCOPED_TRACE("Skipped (SpellFamily): " + std::to_string(skippedFamily)); + SCOPED_TRACE("Skipped (NoFlags): " + std::to_string(skippedNoFlags)); + + // Expect high pass rate for entries we can test + EXPECT_GT(passed, totalTested / 2) << "At least half of tested entries should pass"; +} + +TEST_F(SpellProcDatabaseTest, AllEntriesWithProcFlags_NegativeTest) +{ + int totalTested = 0; + int correctlyRejected = 0; + + for (auto const& entry : _allEntries) + { + if (entry.ProcFlags == 0) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + // Find a flag that's NOT in this entry's ProcFlags + uint32 wrongFlag = 0; + for (auto const& scenario : PROC_FLAG_SCENARIOS) + { + if (!(entry.ProcFlags & scenario.procFlag) && scenario.procFlag != PROC_FLAG_KILL) + { + wrongFlag = scenario.procFlag; + break; + } + } + + if (wrongFlag == 0) + continue; + + totalTested++; + + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(wrongFlag) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + if (!sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + { + correctlyRejected++; + } + } + + float rejectRate = totalTested > 0 ? (float)correctlyRejected / totalTested * 100 : 0; + SCOPED_TRACE("Tested: " + std::to_string(totalTested)); + SCOPED_TRACE("Rejected: " + std::to_string(correctlyRejected) + " (" + std::to_string((int)rejectRate) + "%)"); + + EXPECT_GT(rejectRate, 90.0f) << "Most entries should reject non-matching proc flags"; +} + +// ============================================================================= +// Tests by Proc Flag Type +// ============================================================================= + +TEST_F(SpellProcDatabaseTest, MeleeProcs_AllTriggerOnMelee) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.ProcFlags & PROC_FLAG_DONE_MELEE_AUTO_ATTACK)) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + uint32 hitMask = entry.HitMask != 0 ? (entry.HitMask & -entry.HitMask) : PROC_HIT_NORMAL; + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(hitMask) + .Build(); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Melee procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_EQ(passed, tested); +} + +TEST_F(SpellProcDatabaseTest, SpellDamageProcs_AllTriggerOnSpellDamage) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.ProcFlags & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + uint32 hitMask = entry.HitMask != 0 ? (entry.HitMask & -entry.HitMask) : PROC_HIT_NORMAL; + uint32 spellTypeMask = entry.SpellTypeMask != 0 ? entry.SpellTypeMask : PROC_SPELL_TYPE_DAMAGE; + uint32 spellPhaseMask = entry.SpellPhaseMask != 0 ? entry.SpellPhaseMask : PROC_SPELL_PHASE_HIT; + + auto eventInfo = CreateEventInfo( + PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG, + hitMask, + spellTypeMask, + spellPhaseMask); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Spell damage procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_GT(passed, 0); +} + +TEST_F(SpellProcDatabaseTest, HealProcs_AllTriggerOnHeal) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.ProcFlags & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS)) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + uint32 hitMask = entry.HitMask != 0 ? (entry.HitMask & -entry.HitMask) : PROC_HIT_NORMAL; + uint32 spellTypeMask = entry.SpellTypeMask != 0 ? entry.SpellTypeMask : PROC_SPELL_TYPE_HEAL; + uint32 spellPhaseMask = entry.SpellPhaseMask != 0 ? entry.SpellPhaseMask : PROC_SPELL_PHASE_HIT; + + auto eventInfo = CreateEventInfo( + PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS, + hitMask, + spellTypeMask, + spellPhaseMask); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Heal procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_GT(passed, 0); +} + +TEST_F(SpellProcDatabaseTest, PeriodicProcs_AllTriggerOnPeriodic) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.ProcFlags & PROC_FLAG_DONE_PERIODIC)) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + uint32 hitMask = entry.HitMask != 0 ? (entry.HitMask & -entry.HitMask) : PROC_HIT_NORMAL; + uint32 spellTypeMask = entry.SpellTypeMask != 0 ? entry.SpellTypeMask : PROC_SPELL_TYPE_DAMAGE; + uint32 spellPhaseMask = entry.SpellPhaseMask != 0 ? entry.SpellPhaseMask : PROC_SPELL_PHASE_HIT; + + auto eventInfo = CreateEventInfo( + PROC_FLAG_DONE_PERIODIC, + hitMask, + spellTypeMask, + spellPhaseMask); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Periodic procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_GT(passed, 0); +} + +TEST_F(SpellProcDatabaseTest, KillProcs_AllTriggerOnKill) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.ProcFlags & PROC_FLAG_KILL)) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_KILL) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Kill procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + // Kill events always proc + EXPECT_EQ(passed, tested); +} + +// ============================================================================= +// Tests by Hit Mask Type +// ============================================================================= + +TEST_F(SpellProcDatabaseTest, CritOnlyProcs_OnlyTriggerOnCrit) +{ + int tested = 0, critPassed = 0, normalRejected = 0; + + for (auto const& entry : _allEntries) + { + // Only entries with EXACTLY crit requirement + if (entry.HitMask != PROC_HIT_CRITICAL) + continue; + if (entry.ProcFlags == 0) + continue; + if (RequiresSpellFamilyMatch(entry)) + continue; + + ProcFlagScenario const* scenario = FindMatchingScenario(entry.ProcFlags); + if (!scenario) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + // Test crit - should pass + auto critEvent = CreateEventInfo( + scenario->procFlag, + PROC_HIT_CRITICAL, + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, critEvent)) + critPassed++; + + // Test normal - should fail + auto normalEvent = CreateEventInfo( + scenario->procFlag, + PROC_HIT_NORMAL, + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (!sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, normalEvent)) + normalRejected++; + } + + SCOPED_TRACE("Crit-only procs: " + std::to_string(tested) + " tested"); + SCOPED_TRACE("Crit passed: " + std::to_string(critPassed)); + SCOPED_TRACE("Normal rejected: " + std::to_string(normalRejected)); + + if (tested > 0) + { + // Most crit-only entries should work, but some may have additional requirements + EXPECT_GT(critPassed, 0) << "At least some crit-only procs should trigger on crits"; + EXPECT_GT(normalRejected, 0) << "At least some crit-only procs should NOT trigger on normal hits"; + } +} + +TEST_F(SpellProcDatabaseTest, DodgeProcs_OnlyTriggerOnDodge) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.HitMask & PROC_HIT_DODGE)) + continue; + if (entry.ProcFlags == 0) + continue; + + ProcFlagScenario const* scenario = FindMatchingScenario(entry.ProcFlags); + if (!scenario) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = CreateEventInfo( + scenario->procFlag, + PROC_HIT_DODGE, + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Dodge procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_EQ(passed, tested); +} + +TEST_F(SpellProcDatabaseTest, ParryProcs_OnlyTriggerOnParry) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.HitMask & PROC_HIT_PARRY)) + continue; + if (entry.ProcFlags == 0) + continue; + + ProcFlagScenario const* scenario = FindMatchingScenario(entry.ProcFlags); + if (!scenario) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = CreateEventInfo( + scenario->procFlag, + PROC_HIT_PARRY, + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Parry procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_EQ(passed, tested); +} + +TEST_F(SpellProcDatabaseTest, BlockProcs_OnlyTriggerOnBlock) +{ + int tested = 0, passed = 0; + + for (auto const& entry : _allEntries) + { + if (!(entry.HitMask & PROC_HIT_BLOCK)) + continue; + if (entry.ProcFlags == 0) + continue; + + ProcFlagScenario const* scenario = FindMatchingScenario(entry.ProcFlags); + if (!scenario) + continue; + + tested++; + SpellProcEntry procEntry = entry.ToSpellProcEntry(); + + auto eventInfo = CreateEventInfo( + scenario->procFlag, + PROC_HIT_BLOCK, + GetEffectiveSpellTypeMask(entry, scenario), + GetEffectiveSpellPhaseMask(entry, scenario)); + + if (sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + passed++; + } + + SCOPED_TRACE("Block procs: " + std::to_string(tested) + " tested, " + std::to_string(passed) + " passed"); + if (tested > 0) + EXPECT_EQ(passed, tested); +} + +// ============================================================================= +// Tests by Spell Family (Class-Specific) +// ============================================================================= + +TEST_F(SpellProcDatabaseTest, GroupBySpellFamily_Statistics) +{ + std::map familyNames = { + {0, "Generic"}, {3, "Mage"}, {4, "Warrior"}, {5, "Warlock"}, + {6, "Priest"}, {7, "Druid"}, {8, "Rogue"}, {9, "Hunter"}, + {10, "Paladin"}, {11, "Shaman"}, {15, "DeathKnight"} + }; + + std::map familyCounts; + for (auto const& entry : _allEntries) + { + familyCounts[entry.SpellFamilyName]++; + } + + for (auto const& [family, count] : familyCounts) + { + std::string name = familyNames.count(family) ? familyNames[family] : "Unknown(" + std::to_string(family) + ")"; + SCOPED_TRACE("SpellFamily " + name + ": " + std::to_string(count) + " entries"); + } + + EXPECT_GT(familyCounts.size(), 0u); +} + +TEST_F(SpellProcDatabaseTest, GroupByProcFlags_Statistics) +{ + std::map flagCounts; + for (auto const& entry : _allEntries) + { + flagCounts[entry.ProcFlags]++; + } + + SCOPED_TRACE("Unique ProcFlags patterns: " + std::to_string(flagCounts.size())); + EXPECT_GT(flagCounts.size(), 0u); +} + +TEST_F(SpellProcDatabaseTest, GroupByHitMask_Statistics) +{ + std::map hitCounts; + for (auto const& entry : _allEntries) + { + hitCounts[entry.HitMask]++; + } + + SCOPED_TRACE("Unique HitMask patterns: " + std::to_string(hitCounts.size())); + EXPECT_GT(hitCounts.size(), 0u); +} + +// ============================================================================= +// Data Integrity Tests +// ============================================================================= + +TEST_F(SpellProcDatabaseTest, ToSpellProcEntry_ConversionCorrect) +{ + for (auto const& entry : _allEntries) + { + SpellProcEntry converted = entry.ToSpellProcEntry(); + + EXPECT_EQ(converted.SchoolMask, entry.SchoolMask); + EXPECT_EQ(converted.SpellFamilyName, entry.SpellFamilyName); + EXPECT_EQ(converted.SpellFamilyMask[0], entry.SpellFamilyMask0); + EXPECT_EQ(converted.SpellFamilyMask[1], entry.SpellFamilyMask1); + EXPECT_EQ(converted.SpellFamilyMask[2], entry.SpellFamilyMask2); + EXPECT_EQ(converted.ProcFlags, entry.ProcFlags); + EXPECT_EQ(converted.SpellTypeMask, entry.SpellTypeMask); + EXPECT_EQ(converted.SpellPhaseMask, entry.SpellPhaseMask); + EXPECT_EQ(converted.HitMask, entry.HitMask); + EXPECT_EQ(converted.AttributesMask, entry.AttributesMask); + EXPECT_EQ(converted.Cooldown.count(), static_cast(entry.Cooldown)); + EXPECT_FLOAT_EQ(converted.Chance, entry.Chance); + } +} diff --git a/src/test/server/game/Spells/SpellProcDisableEffectsTest.cpp b/src/test/server/game/Spells/SpellProcDisableEffectsTest.cpp new file mode 100644 index 000000000..e61034fcf --- /dev/null +++ b/src/test/server/game/Spells/SpellProcDisableEffectsTest.cpp @@ -0,0 +1,275 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcDisableEffectsTest.cpp + * @brief Unit tests for DisableEffectsMask filtering in proc system + * + * Tests the logic from SpellAuras.cpp:2244-2258: + * - Bitmask filtering for effect indices 0, 1, 2 + * - Combined filtering with multiple disabled effects + * - Proc blocking when all effects are disabled + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcDisableEffectsTest : public ::testing::Test +{ +protected: + void SetUp() override {} + + // Default initial mask with all 3 effects enabled + static constexpr uint8 ALL_EFFECTS_MASK = 0x07; // 0b111 +}; + +// ============================================================================= +// Single Effect Disable Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, DisableEffect0_BlocksOnlyEffect0) +{ + uint32 disableMask = 0x01; // Disable effect 0 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x06) // 0b110 - effects 1 and 2 remain + << "DisableEffectsMask=0x01 should only disable effect 0"; +} + +TEST_F(SpellProcDisableEffectsTest, DisableEffect1_BlocksOnlyEffect1) +{ + uint32 disableMask = 0x02; // Disable effect 1 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x05) // 0b101 - effects 0 and 2 remain + << "DisableEffectsMask=0x02 should only disable effect 1"; +} + +TEST_F(SpellProcDisableEffectsTest, DisableEffect2_BlocksOnlyEffect2) +{ + uint32 disableMask = 0x04; // Disable effect 2 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x03) // 0b011 - effects 0 and 1 remain + << "DisableEffectsMask=0x04 should only disable effect 2"; +} + +// ============================================================================= +// Multiple Effects Disable Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, DisableEffects0And1_LeavesEffect2) +{ + uint32 disableMask = 0x03; // Disable effects 0 and 1 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x04) // 0b100 - only effect 2 remains + << "DisableEffectsMask=0x03 should leave only effect 2"; +} + +TEST_F(SpellProcDisableEffectsTest, DisableEffects0And2_LeavesEffect1) +{ + uint32 disableMask = 0x05; // Disable effects 0 and 2 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x02) // 0b010 - only effect 1 remains + << "DisableEffectsMask=0x05 should leave only effect 1"; +} + +TEST_F(SpellProcDisableEffectsTest, DisableEffects1And2_LeavesEffect0) +{ + uint32 disableMask = 0x06; // Disable effects 1 and 2 + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x01) // 0b001 - only effect 0 remains + << "DisableEffectsMask=0x06 should leave only effect 0"; +} + +// ============================================================================= +// All Effects Disabled - Proc Blocked +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, DisableAllEffects_BlocksProc) +{ + uint32 disableMask = 0x07; // Disable all 3 effects + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x00) + << "DisableEffectsMask=0x07 should disable all effects"; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, disableMask)) + << "Proc should be blocked when all effects are disabled"; +} + +TEST_F(SpellProcDisableEffectsTest, NotAllDisabled_ProcAllowed) +{ + // Only effect 0 disabled + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, 0x01)) + << "Proc should be allowed when some effects remain"; + + // Only effects 0 and 1 disabled + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, 0x03)) + << "Proc should be allowed when effect 2 remains"; + + // No effects disabled + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, 0x00)) + << "Proc should be allowed when no effects are disabled"; +} + +// ============================================================================= +// Partial Initial Mask Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, PartialInitialMask_Effect0Only) +{ + uint8 initialMask = 0x01; // Only effect 0 enabled + + // Disabling effect 0 should result in 0 + EXPECT_EQ(ProcChanceTestHelper::ApplyDisableEffectsMask(initialMask, 0x01), 0x00); + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(initialMask, 0x01)); + + // Disabling effect 1 should have no impact + EXPECT_EQ(ProcChanceTestHelper::ApplyDisableEffectsMask(initialMask, 0x02), 0x01); + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(initialMask, 0x02)); +} + +TEST_F(SpellProcDisableEffectsTest, PartialInitialMask_Effects0And1) +{ + uint8 initialMask = 0x03; // Effects 0 and 1 enabled + + // Disabling both should result in 0 + EXPECT_EQ(ProcChanceTestHelper::ApplyDisableEffectsMask(initialMask, 0x03), 0x00); + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(initialMask, 0x03)); + + // Disabling only effect 0 should leave effect 1 + EXPECT_EQ(ProcChanceTestHelper::ApplyDisableEffectsMask(initialMask, 0x01), 0x02); + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(initialMask, 0x01)); +} + +// ============================================================================= +// Zero Disable Mask Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, ZeroDisableMask_NoEffectDisabled) +{ + uint32 disableMask = 0x00; // Nothing disabled + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, ALL_EFFECTS_MASK) + << "Zero DisableEffectsMask should leave all effects enabled"; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, disableMask)) + << "Proc should be allowed when nothing is disabled"; +} + +// ============================================================================= +// Higher Bits Ignored Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, HigherBits_IgnoredForEffects) +{ + // Bits beyond 0x07 should be ignored (only 3 effects exist) + uint32 disableMask = 0xFF; // All bits set + + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, disableMask); + + EXPECT_EQ(result, 0x00) + << "Only lower 3 bits should affect the result"; + + // Only lower bits matter + uint32 highBitsOnly = 0xF8; // High bits only + result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, highBitsOnly); + + EXPECT_EQ(result, ALL_EFFECTS_MASK) + << "High bits (0xF8) should not affect lower 3 effects"; +} + +// ============================================================================= +// Integration with SpellProcEntry Tests +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, SpellProcEntry_WithDisableEffectsMask) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithDisableEffectsMask(0x05) // Disable effects 0 and 2 + .WithChance(100.0f) + .Build(); + + // Verify the mask was set correctly + EXPECT_EQ(procEntry.DisableEffectsMask, 0x05u); + + // Apply to initial mask + uint8 result = ProcChanceTestHelper::ApplyDisableEffectsMask(ALL_EFFECTS_MASK, procEntry.DisableEffectsMask); + + EXPECT_EQ(result, 0x02) // Only effect 1 remains + << "SpellProcEntry DisableEffectsMask should filter correctly"; +} + +TEST_F(SpellProcDisableEffectsTest, SpellProcEntry_AllDisabled) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithDisableEffectsMask(0x07) // Disable all effects + .WithChance(100.0f) + .Build(); + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(ALL_EFFECTS_MASK, procEntry.DisableEffectsMask)) + << "Proc should be blocked when all effects disabled in SpellProcEntry"; +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcDisableEffectsTest, Scenario_SingleEffectAura) +{ + // Many procs only have a single effect that matters + uint8 singleEffectMask = 0x01; // Only effect 0 + + // Disabling effect 0 blocks the proc + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(singleEffectMask, 0x01)); + + // Disabling other effects has no impact + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(singleEffectMask, 0x02)); + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(singleEffectMask, 0x04)); + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(singleEffectMask, 0x06)); +} + +TEST_F(SpellProcDisableEffectsTest, Scenario_DualEffectAura) +{ + // Aura with effects 0 and 1 (healing + damage proc for example) + uint8 dualEffectMask = 0x03; + + // Disabling one effect leaves the other + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(dualEffectMask, 0x01)); + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(dualEffectMask, 0x02)); + + // Disabling both blocks the proc + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToDisabledEffects(dualEffectMask, 0x03)); +} diff --git a/src/test/server/game/Spells/SpellProcEquipmentTest.cpp b/src/test/server/game/Spells/SpellProcEquipmentTest.cpp new file mode 100644 index 000000000..addd8a492 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcEquipmentTest.cpp @@ -0,0 +1,404 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcEquipmentTest.cpp + * @brief Unit tests for equipment requirement validation in proc system + * + * Tests the logic from SpellAuras.cpp:2260-2298: + * - Weapon class requirement validation + * - Armor class requirement validation + * - Attack type to slot mapping + * - Feral form blocking weapon procs + * - Broken item blocking procs + * - SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT bypass + * - Item subclass mask validation + * + * ============================================================================ + * TEST DESIGN: Configuration-Based Testing + * ============================================================================ + * + * These tests use EquipmentConfig structs to simulate different equipment + * scenarios without requiring actual game objects. Each test configures: + * - isPassive: Whether the aura is passive (equipment check only applies to passive) + * - isPlayer: Whether the target is a player (NPCs skip equipment checks) + * - equippedItemClass: ITEM_CLASS_WEAPON, ITEM_CLASS_ARMOR, or ITEM_CLASS_ANY + * - hasEquippedItem: Whether the required item slot has an item + * - itemIsBroken: Whether the equipped item is broken (0 durability) + * - itemFitsRequirements: Whether the item matches subclass mask requirements + * - isInFeralForm: Whether a druid is in cat/bear form (blocks weapon procs) + * - hasNoEquipRequirementAttr: SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT bypass + * + * No GTEST_SKIP() is used in this file - all tests run with their configured + * scenarios, testing both positive and negative cases explicitly. + * ============================================================================ + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcEquipmentTest : public ::testing::Test +{ +protected: + void SetUp() override {} + + // Create default config for weapon proc + ProcChanceTestHelper::EquipmentConfig CreateWeaponProcConfig() + { + ProcChanceTestHelper::EquipmentConfig config; + config.isPassive = true; + config.isPlayer = true; + config.equippedItemClass = ProcChanceTestHelper::ITEM_CLASS_WEAPON; + config.hasEquippedItem = true; + config.itemIsBroken = false; + config.itemFitsRequirements = true; + return config; + } + + // Create default config for armor proc + ProcChanceTestHelper::EquipmentConfig CreateArmorProcConfig() + { + ProcChanceTestHelper::EquipmentConfig config; + config.isPassive = true; + config.isPlayer = true; + config.equippedItemClass = ProcChanceTestHelper::ITEM_CLASS_ARMOR; + config.hasEquippedItem = true; + config.itemIsBroken = false; + config.itemFitsRequirements = true; + return config; + } +}; + +// ============================================================================= +// No Equipment Requirement Tests +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, NoEquipRequirement_AllowsProc) +{ + ProcChanceTestHelper::EquipmentConfig config; + config.isPassive = true; + config.isPlayer = true; + config.equippedItemClass = ProcChanceTestHelper::ITEM_CLASS_ANY; // No requirement + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "No equipment requirement should allow proc"; +} + +TEST_F(SpellProcEquipmentTest, NonPassiveAura_SkipsCheck) +{ + ProcChanceTestHelper::EquipmentConfig config; + config.isPassive = false; // Not a passive aura + config.isPlayer = true; + config.equippedItemClass = ProcChanceTestHelper::ITEM_CLASS_WEAPON; + config.hasEquippedItem = false; // Would normally block + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Non-passive aura should skip equipment check"; +} + +TEST_F(SpellProcEquipmentTest, NonPlayerTarget_SkipsCheck) +{ + ProcChanceTestHelper::EquipmentConfig config; + config.isPassive = true; + config.isPlayer = false; // NPC/creature + config.equippedItemClass = ProcChanceTestHelper::ITEM_CLASS_WEAPON; + config.hasEquippedItem = false; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Non-player target should skip equipment check"; +} + +// ============================================================================= +// Weapon Class Requirement Tests +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, WeaponRequired_WithWeapon_AllowsProc) +{ + auto config = CreateWeaponProcConfig(); + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Weapon requirement met should allow proc"; +} + +TEST_F(SpellProcEquipmentTest, WeaponRequired_NoWeapon_BlocksProc) +{ + auto config = CreateWeaponProcConfig(); + config.hasEquippedItem = false; // No weapon equipped + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Missing weapon should block proc"; +} + +TEST_F(SpellProcEquipmentTest, WeaponRequired_BrokenWeapon_BlocksProc) +{ + auto config = CreateWeaponProcConfig(); + config.itemIsBroken = true; // Weapon is broken + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Broken weapon should block proc"; +} + +TEST_F(SpellProcEquipmentTest, WeaponRequired_WrongSubclass_BlocksProc) +{ + auto config = CreateWeaponProcConfig(); + config.itemFitsRequirements = false; // Wrong weapon type + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Wrong weapon subclass should block proc"; +} + +// ============================================================================= +// Armor Class Requirement Tests +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, ArmorRequired_WithArmor_AllowsProc) +{ + auto config = CreateArmorProcConfig(); + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Armor requirement met should allow proc"; +} + +TEST_F(SpellProcEquipmentTest, ArmorRequired_NoArmor_BlocksProc) +{ + auto config = CreateArmorProcConfig(); + config.hasEquippedItem = false; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Missing armor should block proc"; +} + +TEST_F(SpellProcEquipmentTest, ArmorRequired_BrokenArmor_BlocksProc) +{ + auto config = CreateArmorProcConfig(); + config.itemIsBroken = true; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Broken armor should block proc"; +} + +// ============================================================================= +// Feral Form Tests - SpellAuras.cpp:2266-2267 +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, FeralForm_WeaponProc_BlocksProc) +{ + auto config = CreateWeaponProcConfig(); + config.isInFeralForm = true; // Druid in cat/bear form + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Feral form should block weapon procs"; +} + +TEST_F(SpellProcEquipmentTest, FeralForm_ArmorProc_AllowsProc) +{ + auto config = CreateArmorProcConfig(); + config.isInFeralForm = true; // Druid in cat/bear form + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Feral form should NOT block armor procs"; +} + +TEST_F(SpellProcEquipmentTest, NotInFeralForm_WeaponProc_AllowsProc) +{ + auto config = CreateWeaponProcConfig(); + config.isInFeralForm = false; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Non-feral form should allow weapon procs"; +} + +// ============================================================================= +// SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT Bypass Tests +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, NoEquipRequirementAttr_BypassesMissingItem) +{ + auto config = CreateWeaponProcConfig(); + config.hasEquippedItem = false; // Would normally block + config.hasNoEquipRequirementAttr = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "NO_PROC_EQUIP_REQUIREMENT should bypass missing item check"; +} + +TEST_F(SpellProcEquipmentTest, NoEquipRequirementAttr_BypassesBrokenItem) +{ + auto config = CreateWeaponProcConfig(); + config.itemIsBroken = true; // Would normally block + config.hasNoEquipRequirementAttr = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "NO_PROC_EQUIP_REQUIREMENT should bypass broken item check"; +} + +TEST_F(SpellProcEquipmentTest, NoEquipRequirementAttr_BypassesFeralForm) +{ + auto config = CreateWeaponProcConfig(); + config.isInFeralForm = true; // Would normally block + config.hasNoEquipRequirementAttr = true; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "NO_PROC_EQUIP_REQUIREMENT should bypass feral form check"; +} + +// ============================================================================= +// Attack Type to Slot Mapping Tests - SpellAuras.cpp:2268-2284 +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, SlotMapping_BaseAttack_MainHand) +{ + uint8 slot = ProcChanceTestHelper::GetWeaponSlotForAttackType(ProcChanceTestHelper::BASE_ATTACK); + EXPECT_EQ(slot, 15) // EQUIPMENT_SLOT_MAINHAND + << "BASE_ATTACK should map to main hand slot"; +} + +TEST_F(SpellProcEquipmentTest, SlotMapping_OffAttack_OffHand) +{ + uint8 slot = ProcChanceTestHelper::GetWeaponSlotForAttackType(ProcChanceTestHelper::OFF_ATTACK); + EXPECT_EQ(slot, 16) // EQUIPMENT_SLOT_OFFHAND + << "OFF_ATTACK should map to off hand slot"; +} + +TEST_F(SpellProcEquipmentTest, SlotMapping_RangedAttack_Ranged) +{ + uint8 slot = ProcChanceTestHelper::GetWeaponSlotForAttackType(ProcChanceTestHelper::RANGED_ATTACK); + EXPECT_EQ(slot, 17) // EQUIPMENT_SLOT_RANGED + << "RANGED_ATTACK should map to ranged slot"; +} + +TEST_F(SpellProcEquipmentTest, SlotMapping_InvalidAttack_DefaultsToMainHand) +{ + uint8 slot = ProcChanceTestHelper::GetWeaponSlotForAttackType(255); // Invalid + EXPECT_EQ(slot, 15) // EQUIPMENT_SLOT_MAINHAND + << "Invalid attack type should default to main hand"; +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, Scenario_WeaponEnchant_Fiery) +{ + // Fiery Weapon enchant - requires melee weapon + auto config = CreateWeaponProcConfig(); + config.attackType = ProcChanceTestHelper::BASE_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Fiery Weapon with main hand should proc"; +} + +TEST_F(SpellProcEquipmentTest, Scenario_WeaponEnchant_FieryOffhand) +{ + // Fiery Weapon on off-hand + auto config = CreateWeaponProcConfig(); + config.attackType = ProcChanceTestHelper::OFF_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Fiery Weapon with off hand should proc"; +} + +TEST_F(SpellProcEquipmentTest, Scenario_Hunter_RangedProc) +{ + // Hunter ranged weapon proc + auto config = CreateWeaponProcConfig(); + config.attackType = ProcChanceTestHelper::RANGED_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Ranged proc with ranged weapon should work"; +} + +TEST_F(SpellProcEquipmentTest, Scenario_FeralDruid_WeaponEnchant) +{ + // Druid with weapon enchant enters cat form + auto config = CreateWeaponProcConfig(); + config.isInFeralForm = true; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Feral druid weapon enchant should be blocked"; +} + +TEST_F(SpellProcEquipmentTest, Scenario_BrokenWeapon_CombatUse) +{ + // Player's weapon breaks during combat + auto config = CreateWeaponProcConfig(); + config.itemIsBroken = true; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Broken weapon procs should be blocked"; +} + +TEST_F(SpellProcEquipmentTest, Scenario_WrongWeaponType) +{ + // Enchant requires sword but player has mace + auto config = CreateWeaponProcConfig(); + config.itemFitsRequirements = false; + + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Wrong weapon type should block proc"; +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcEquipmentTest, EdgeCase_AllConditionsMet) +{ + auto config = CreateWeaponProcConfig(); + // All requirements met + config.isPassive = true; + config.isPlayer = true; + config.hasEquippedItem = true; + config.itemIsBroken = false; + config.itemFitsRequirements = true; + config.isInFeralForm = false; + config.hasNoEquipRequirementAttr = false; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "All conditions met should allow proc"; +} + +TEST_F(SpellProcEquipmentTest, EdgeCase_AllBlockingConditions) +{ + auto config = CreateWeaponProcConfig(); + // Multiple blocking conditions + config.hasEquippedItem = false; + config.itemIsBroken = true; + config.itemFitsRequirements = false; + config.isInFeralForm = true; + + // Should be blocked (first check that fails) + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Multiple blocking conditions should still block"; +} + +TEST_F(SpellProcEquipmentTest, EdgeCase_BypassOverridesAll) +{ + auto config = CreateWeaponProcConfig(); + // Multiple blocking conditions BUT bypass is set + config.hasEquippedItem = false; + config.itemIsBroken = true; + config.itemFitsRequirements = false; + config.isInFeralForm = true; + config.hasNoEquipRequirementAttr = true; // Bypass + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockDueToEquipment(config)) + << "Bypass attribute should override all blocking conditions"; +} diff --git a/src/test/server/game/Spells/SpellProcFullCoverageTest.cpp b/src/test/server/game/Spells/SpellProcFullCoverageTest.cpp new file mode 100644 index 000000000..f4a46d59f --- /dev/null +++ b/src/test/server/game/Spells/SpellProcFullCoverageTest.cpp @@ -0,0 +1,458 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcFullCoverageTest.cpp + * @brief Data-driven tests for ALL 869 spell_proc entries + * + * Tests proc calculations for every spell_proc entry: + * - Cooldown blocking behavior + * - Chance calculation with level reduction + * - Attribute flag validation + * + * This complements SpellProcDataDrivenTest.cpp which tests CanSpellTriggerProcOnEvent(). + * + * ============================================================================ + * DESIGN NOTE: Why Tests Skip Certain Entries + * ============================================================================ + * + * This test file uses parameterized tests that run against ALL 869 spell_proc + * entries. Each test validates a specific feature (cooldowns, level reduction, + * attribute flags, etc.). Tests use GTEST_SKIP() for entries that don't have + * the feature being tested. + * + * For example (current counts from test output): + * - CooldownBlocking_WhenCooldownSet: Tests 246 entries with Cooldown > 0 (skips 623) + * - Level60Reduction_WhenAttributeSet: Tests entries with PROC_ATTR_REDUCE_PROC_60 (0 currently) + * - UseStacksForCharges_Behavior: Tests entries with PROC_ATTR_USE_STACKS_FOR_CHARGES (0 currently) + * - TriggeredCanProc_FlagSet: Tests 73 entries with PROC_ATTR_TRIGGERED_CAN_PROC (skips 796) + * - ReqManaCost_FlagSet: Tests 5 entries with PROC_ATTR_REQ_MANA_COST (skips 864) + * + * This is INTENTIONAL. Running parameterized tests against all entries ensures: + * 1. Every entry is validated for applicable features + * 2. Statistics show exact coverage (X entries with feature Y) + * 3. New entries added to spell_proc are automatically tested + * 4. Regression detection if an entry unexpectedly gains/loses a feature + * + * The statistics tests at the bottom output the exact counts: + * "[ INFO ] Entries with cooldown: 85 / 869" + * "[ INFO ] Entries with REDUCE_PROC_60: 15 / 869" + * etc. + * + * SKIPPED tests are expected and correct. Each skip message includes: + * - The SpellId being skipped + * - The reason (e.g., "has no cooldown", "doesn't have REDUCE_PROC_60") + * ============================================================================ + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "SpellProcTestData.h" +#include "AuraStub.h" +#include "gtest/gtest.h" + +using namespace testing; +using namespace std::chrono_literals; + +// ============================================================================= +// Parameterized Test Fixture for ALL Entries +// ============================================================================= + +class SpellProcFullCoverageTest : public ::testing::TestWithParam +{ +protected: + void SetUp() override + { + _entry = GetParam(); + _procEntry = _entry.ToSpellProcEntry(); + } + + SpellProcTestEntry _entry; + SpellProcEntry _procEntry; +}; + +// ============================================================================= +// Cooldown Tests - ALL entries with Cooldown > 0 +// 246 of 869 entries have cooldowns (Internal Cooldowns / ICDs) +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, CooldownBlocking_WhenCooldownSet) +{ + // SKIP REASON: This test validates cooldown blocking behavior. + // Only entries with Cooldown > 0 can be tested for ICD (Internal Cooldown). + // Entries without cooldowns proc on every valid trigger, so there's nothing + // to test here. The skip count shows how many entries lack cooldowns. + if (_entry.Cooldown == 0) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no cooldown"; + + ProcTestScenario scenario; + scenario.WithAura(std::abs(_entry.SpellId)); + + // Set 100% chance to isolate cooldown testing + SpellProcEntry testEntry = _procEntry; + testEntry.Chance = 100.0f; + testEntry.Cooldown = Milliseconds(_entry.Cooldown); + + // First proc should succeed + EXPECT_TRUE(scenario.SimulateProc(testEntry)) + << "SpellId " << _entry.SpellId << " first proc should succeed"; + + // Second proc immediately after should fail (on cooldown) + EXPECT_FALSE(scenario.SimulateProc(testEntry)) + << "SpellId " << _entry.SpellId << " should be blocked during " + << _entry.Cooldown << "ms cooldown"; + + // Wait for cooldown to expire + scenario.AdvanceTime(std::chrono::milliseconds(_entry.Cooldown + 1)); + + // Third proc after cooldown should succeed + EXPECT_TRUE(scenario.SimulateProc(testEntry)) + << "SpellId " << _entry.SpellId << " should proc after cooldown expires"; +} + +// ============================================================================= +// Level 60+ Reduction Tests - ALL entries with PROC_ATTR_REDUCE_PROC_60 +// Currently 0 of 869 entries use this attribute (data may need population). +// This attribute reduces proc chance by 3.333% per level above 60. +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, Level60Reduction_WhenAttributeSet) +{ + // SKIP REASON: This test validates the level 60+ proc chance reduction formula. + // Only entries with PROC_ATTR_REDUCE_PROC_60 attribute have their proc chance + // reduced at higher levels. Spells like old weapon procs (Fiery, Crusader) + // use this to prevent them from being overpowered at level 80. + // Entries without this attribute maintain constant proc chance at all levels. + if (!(_entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60)) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have REDUCE_PROC_60"; + + // Use a meaningful base chance for testing + float baseChance = _entry.Chance > 0 ? _entry.Chance : 30.0f; + + // Level 60: No reduction + float chanceAt60 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 60); + EXPECT_NEAR(chanceAt60, baseChance, 0.01f) + << "SpellId " << _entry.SpellId << " should have no reduction at level 60"; + + // Level 70: 33.33% reduction + float chanceAt70 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 70); + float expectedAt70 = baseChance * (1.0f - 10.0f/30.0f); + EXPECT_NEAR(chanceAt70, expectedAt70, 0.5f) + << "SpellId " << _entry.SpellId << " should have 33% reduction at level 70"; + + // Level 80: 66.67% reduction + float chanceAt80 = ProcChanceTestHelper::ApplyLevel60Reduction(baseChance, 80); + float expectedAt80 = baseChance * (1.0f - 20.0f/30.0f); + EXPECT_NEAR(chanceAt80, expectedAt80, 0.5f) + << "SpellId " << _entry.SpellId << " should have 66% reduction at level 80"; + + // Verify reduction is correct + EXPECT_LT(chanceAt80, chanceAt70) + << "SpellId " << _entry.SpellId << " chance at 80 should be less than at 70"; + EXPECT_LT(chanceAt70, chanceAt60) + << "SpellId " << _entry.SpellId << " chance at 70 should be less than at 60"; +} + +// ============================================================================= +// Attribute Validation Tests - ALL entries +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, AttributeMask_ValidFlags) +{ + // Valid attribute flags + constexpr uint32 VALID_ATTRIBUTE_MASK = + PROC_ATTR_REQ_EXP_OR_HONOR | + PROC_ATTR_TRIGGERED_CAN_PROC | + PROC_ATTR_REQ_MANA_COST | + PROC_ATTR_REQ_SPELLMOD | + PROC_ATTR_USE_STACKS_FOR_CHARGES | + PROC_ATTR_REDUCE_PROC_60 | + PROC_ATTR_CANT_PROC_FROM_ITEM_CAST; + + // Check for invalid bits (skip 0x20 and 0x40 which are unused/reserved) + uint32 invalidBits = _entry.AttributesMask & ~VALID_ATTRIBUTE_MASK & ~0x60; + EXPECT_EQ(invalidBits, 0u) + << "SpellId " << _entry.SpellId << " has invalid attribute bits: 0x" + << std::hex << invalidBits; +} + +TEST_P(SpellProcFullCoverageTest, UseStacksForCharges_Behavior) +{ + // SKIP REASON: This test validates stack consumption instead of charge consumption. + // Currently 0 entries use PROC_ATTR_USE_STACKS_FOR_CHARGES (attribute data may + // need population). When set, this causes procs to decrement the aura's stack + // count rather than its charge count. + // Example: Druid's Eclipse - each proc reduces stacks until buff expires. + // Most proc auras use charges (consumed individually) not stacks. + if (!(_entry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES)) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't use stacks for charges"; + + auto aura = AuraStubBuilder() + .WithId(std::abs(_entry.SpellId)) + .WithStackAmount(5) + .Build(); + + SpellProcEntry testEntry = _procEntry; + testEntry.Chance = 100.0f; + + // Consume should decrement stacks + bool removed = ProcChanceTestHelper::SimulateConsumeProcCharges(aura.get(), testEntry); + + EXPECT_EQ(aura->GetStackAmount(), 4) + << "SpellId " << _entry.SpellId << " should decrement stacks"; + EXPECT_FALSE(removed); +} + +TEST_P(SpellProcFullCoverageTest, TriggeredCanProc_FlagSet) +{ + // SKIP REASON: This test validates the PROC_ATTR_TRIGGERED_CAN_PROC attribute. + // Most proc auras (796 entries) do NOT allow triggered spells to trigger them, + // preventing infinite proc chains. Only 73 entries explicitly allow triggered + // spells to proc (e.g., some talent effects that should chain-react). + // Entries without this flag block triggered spell procs for safety. + if (!(_entry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC)) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have TRIGGERED_CAN_PROC"; + + // Just verify the flag is properly set in the entry + EXPECT_TRUE(_procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) + << "SpellId " << _entry.SpellId << " TRIGGERED_CAN_PROC should be set"; +} + +TEST_P(SpellProcFullCoverageTest, ReqManaCost_FlagSet) +{ + // SKIP REASON: This test validates the PROC_ATTR_REQ_MANA_COST attribute. + // Only 5 entries require the triggering spell to have a mana cost. + // This prevents free spells (instant casts with no cost) from triggering procs. + // Example: Illumination should only proc from actual heals, not free procs. + // 864 entries don't care about mana cost, so this test is skipped for them. + if (!(_entry.AttributesMask & PROC_ATTR_REQ_MANA_COST)) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " doesn't have REQ_MANA_COST"; + + // Just verify the flag is properly set in the entry + EXPECT_TRUE(_procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST) + << "SpellId " << _entry.SpellId << " REQ_MANA_COST should be set"; +} + +// ============================================================================= +// Chance Calculation Tests - ALL entries with Chance > 0 +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, ChanceValue_InValidRange) +{ + // Chance should be in valid range (0-100 normally, but some can exceed) + // Just verify it's not negative + EXPECT_GE(_entry.Chance, 0.0f) + << "SpellId " << _entry.SpellId << " has negative chance"; + + // And not absurdly high (>500% would be suspicious) + EXPECT_LE(_entry.Chance, 500.0f) + << "SpellId " << _entry.SpellId << " has suspiciously high chance"; +} + +TEST_P(SpellProcFullCoverageTest, ChanceCalculation_WithEntry) +{ + // SKIP REASON: This test validates proc chance calculation with level reduction. + // Entries with Chance = 0 rely on DBC defaults or use PPM (procs per minute) instead. + // We can only test explicit chance calculation for entries that define a Chance value. + // PPM-based procs are tested separately in SpellProcPPMTest.cpp. + if (_entry.Chance <= 0.0f) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no base chance"; + + // Calculate chance at level 80 (typical max level) + float calculatedChance = ProcChanceTestHelper::SimulateCalcProcChance( + _procEntry, 80, 2500, 0.0f, 0.0f, false); + + if (_entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60) + { + // With level 60+ reduction at level 80 + float expectedReduced = _entry.Chance * (1.0f - 20.0f/30.0f); + EXPECT_NEAR(calculatedChance, expectedReduced, 0.5f) + << "SpellId " << _entry.SpellId << " reduced chance mismatch"; + } + else + { + // Without reduction + EXPECT_NEAR(calculatedChance, _entry.Chance, 0.01f) + << "SpellId " << _entry.SpellId << " base chance mismatch"; + } +} + +// ============================================================================= +// ProcFlags Validation Tests - ALL entries +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, ProcFlags_NotEmpty) +{ + // Most entries should have proc flags OR spell family filters + // Skip validation if both are zero (some entries use only SchoolMask) + if (_entry.ProcFlags == 0 && _entry.SpellFamilyName == 0 && _entry.SchoolMask == 0) + { + // This is a potential configuration issue, but not necessarily an error + // Some entries are passive effects that don't proc from events + } + + // Just verify ProcFlags is valid (no invalid bits) + // All valid proc flags are defined in SpellMgr.h + // This is a basic sanity check +} + +// ============================================================================= +// Cooldown Value Validation Tests - ALL entries with cooldown +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, CooldownValue_Reasonable) +{ + // SKIP REASON: This test validates cooldown values are within reasonable bounds. + // Entries without cooldowns (Cooldown = 0) can proc on every trigger with no + // internal cooldown. 623 entries have no ICD and this is intentional - they + // rely on proc chance alone to limit frequency. + // Only 246 entries with explicit cooldowns need range validation. + if (_entry.Cooldown == 0) + GTEST_SKIP() << "SpellId " << _entry.SpellId << " has no cooldown"; + + // Cooldowns should be reasonable (not too short, not too long) + // Shortest reasonable cooldown is ~1ms + // Longest reasonable cooldown is ~15 minutes (900000ms) - some trinkets have 10+ min ICDs + EXPECT_GE(_entry.Cooldown, 1u) + << "SpellId " << _entry.SpellId << " has suspiciously short cooldown"; + EXPECT_LE(_entry.Cooldown, 900000u) + << "SpellId " << _entry.SpellId << " has suspiciously long cooldown (" + << _entry.Cooldown << "ms = " << _entry.Cooldown/60000 << " minutes)"; +} + +// ============================================================================= +// SpellId Validation Tests - ALL entries +// ============================================================================= + +TEST_P(SpellProcFullCoverageTest, SpellId_NonZero) +{ + // SpellId should never be zero + EXPECT_NE(_entry.SpellId, 0) + << "Entry has zero SpellId which is invalid"; +} + +// ============================================================================= +// Test Instantiation - ALL 869 entries +// ============================================================================= + +INSTANTIATE_TEST_SUITE_P( + AllSpellProcEntries, + SpellProcFullCoverageTest, + ::testing::ValuesIn(GetAllSpellProcTestEntries()), + [](const ::testing::TestParamInfo& info) { + // Generate unique test name from spell ID + int32_t id = info.param.SpellId; + if (id < 0) + return "NegId_" + std::to_string(-id); + return "SpellId_" + std::to_string(id); + } +); + +// ============================================================================= +// Statistics Tests - Run once to summarize coverage +// ============================================================================= + +class SpellProcCoverageStatsTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _allEntries = GetAllSpellProcTestEntries(); + } + + std::vector _allEntries; +}; + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithCooldown) +{ + size_t withCooldown = 0; + for (auto const& entry : _allEntries) + { + if (entry.Cooldown > 0) + ++withCooldown; + } + std::cout << "[ INFO ] Entries with cooldown: " << withCooldown + << " / " << _allEntries.size() << std::endl; + EXPECT_GT(withCooldown, 0u); +} + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithChance) +{ + size_t withChance = 0; + for (auto const& entry : _allEntries) + { + if (entry.Chance > 0.0f) + ++withChance; + } + std::cout << "[ INFO ] Entries with chance > 0: " << withChance + << " / " << _allEntries.size() << std::endl; +} + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithLevel60Reduction) +{ + size_t withReduction = 0; + for (auto const& entry : _allEntries) + { + if (entry.AttributesMask & PROC_ATTR_REDUCE_PROC_60) + ++withReduction; + } + std::cout << "[ INFO ] Entries with REDUCE_PROC_60: " << withReduction + << " / " << _allEntries.size() << std::endl; +} + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithUseStacks) +{ + size_t withUseStacks = 0; + for (auto const& entry : _allEntries) + { + if (entry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES) + ++withUseStacks; + } + std::cout << "[ INFO ] Entries with USE_STACKS_FOR_CHARGES: " << withUseStacks + << " / " << _allEntries.size() << std::endl; +} + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithTriggeredCanProc) +{ + size_t withTriggered = 0; + for (auto const& entry : _allEntries) + { + if (entry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) + ++withTriggered; + } + std::cout << "[ INFO ] Entries with TRIGGERED_CAN_PROC: " << withTriggered + << " / " << _allEntries.size() << std::endl; +} + +TEST_F(SpellProcCoverageStatsTest, CountEntriesWithReqManaCost) +{ + size_t withReqManaCost = 0; + for (auto const& entry : _allEntries) + { + if (entry.AttributesMask & PROC_ATTR_REQ_MANA_COST) + ++withReqManaCost; + } + std::cout << "[ INFO ] Entries with REQ_MANA_COST: " << withReqManaCost + << " / " << _allEntries.size() << std::endl; +} + +TEST_F(SpellProcCoverageStatsTest, TotalEntryCount) +{ + std::cout << "[ INFO ] Total spell_proc entries tested: " << _allEntries.size() << std::endl; + EXPECT_EQ(_allEntries.size(), 869u) + << "Expected 869 entries but got " << _allEntries.size(); +} diff --git a/src/test/server/game/Spells/SpellProcIntegrationTest.cpp b/src/test/server/game/Spells/SpellProcIntegrationTest.cpp new file mode 100644 index 000000000..11aeeba67 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcIntegrationTest.cpp @@ -0,0 +1,565 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AuraScriptTestFramework.h" +#include "SpellMgr.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +using namespace testing; + +/** + * @brief Integration tests for the proc system + * + * These tests verify that the proc system correctly integrates: + * - SpellProcEntry configuration + * - CanSpellTriggerProcOnEvent logic + * - Proc flag combinations + * - Spell family matching + * - Hit mask filtering + */ +class SpellProcIntegrationTest : public AuraScriptProcTestFixture +{ +protected: + void SetUp() override + { + AuraScriptProcTestFixture::SetUp(); + } +}; + +// ============================================================================= +// Melee Attack Proc Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, MeleeAutoAttackProc_NormalHit) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL | PROC_HIT_CRITICAL) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithNormalHit(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, MeleeAutoAttackProc_CritOnly) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + // Normal hit should NOT trigger crit-only proc + auto normalScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithNormalHit(); + EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, normalScenario); + + // Critical hit should trigger + auto critScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithCrit(); + EXPECT_PROC_TRIGGERS(procEntry, critScenario); +} + +TEST_F(SpellProcIntegrationTest, MeleeAutoAttackProc_Miss) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_MISS) + .Build(); + + auto missScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithMiss(); + + EXPECT_PROC_TRIGGERS(procEntry, missScenario); +} + +// ============================================================================= +// Spell Damage Proc Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, SpellDamageProc_OnHit) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnSpellDamage() + .OnHit() + .WithNormalHit(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, SpellDamageProc_OnCast) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .Build(); + + // Should trigger on cast phase + auto castScenario = ProcScenarioBuilder() + .OnSpellDamage() + .OnCast(); + EXPECT_PROC_TRIGGERS(procEntry, castScenario); + + // Should NOT trigger on hit phase when configured for cast only + auto hitScenario = ProcScenarioBuilder() + .OnSpellDamage() + .OnHit(); + EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, hitScenario); +} + +// ============================================================================= +// Heal Proc Tests +// ============================================================================= + +// Heal proc tests - require SpellPhaseMask to be set +TEST_F(SpellProcIntegrationTest, HealProc_OnHeal) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnHeal() + .OnHit(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, HealProc_CritHeal) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithHitMask(PROC_HIT_CRITICAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + // Normal heal should NOT trigger crit-only proc + auto normalScenario = ProcScenarioBuilder() + .OnHeal() + .WithNormalHit(); + EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, normalScenario); + + // Crit heal should trigger + auto critScenario = ProcScenarioBuilder() + .OnHeal() + .WithCrit(); + EXPECT_PROC_TRIGGERS(procEntry, critScenario); +} + +// ============================================================================= +// Periodic Effect Proc Tests +// ============================================================================= + +// Periodic proc tests - spell procs that require SpellPhaseMask to be set +TEST_F(SpellProcIntegrationTest, PeriodicDamageProc) +{ + // Note: PROC_FLAG_DONE_PERIODIC is in REQ_SPELL_PHASE_PROC_FLAG_MASK, + // so SpellPhaseMask must be set (can't be 0) + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_PERIODIC) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnPeriodicDamage() + .WithNormalHit(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, PeriodicHealProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_PERIODIC) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnPeriodicHeal() + .WithNormalHit(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +// ============================================================================= +// Kill/Death Proc Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, KillProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_KILL) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnKill(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, DeathProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DEATH) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnDeath(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +// ============================================================================= +// Defensive Proc Tests (Dodge/Parry/Block) +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, DodgeProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_DODGE) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnTakenMeleeAutoAttack() + .WithDodge(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, ParryProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_PARRY) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnTakenMeleeAutoAttack() + .WithParry(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, BlockProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_BLOCK) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnTakenMeleeAutoAttack() + .WithBlock(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +TEST_F(SpellProcIntegrationTest, FullBlockProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_FULL_BLOCK) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnTakenMeleeAutoAttack() + .WithFullBlock(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +// ============================================================================= +// Absorb Proc Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, AbsorbProc) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_ABSORB) + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnTakenSpellDamage() + .WithAbsorb(); + + EXPECT_PROC_TRIGGERS(procEntry, scenario); +} + +// Note: PROC_HIT_ABSORB covers both partial and full absorb +// There is no separate PROC_HIT_FULL_ABSORB flag in AzerothCore + +// ============================================================================= +// Spell Family Filtering Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, SpellFamilyMatch_SameFamily) +{ + // Create a Mage spell (family 3) + auto* triggerSpell = CreateSpellInfo(133, 3, 0x00000001); // Fireball + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellFamilyName(3) // SPELLFAMILY_MAGE + .WithSpellFamilyMask(flag96(0x00000001, 0, 0)) + .Build(); + + // Test family match logic + EXPECT_TRUE(TestSpellFamilyMatch(procEntry.SpellFamilyName, procEntry.SpellFamilyMask, triggerSpell)); +} + +TEST_F(SpellProcIntegrationTest, SpellFamilyMatch_DifferentFamily) +{ + // Create a Warrior spell (family 4) + auto* triggerSpell = CreateSpellInfo(6343, 4, 0x00000001); // Thunder Clap + + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(3) // SPELLFAMILY_MAGE - should NOT match + .WithSpellFamilyMask(flag96(0x00000001, 0, 0)) + .Build(); + + EXPECT_FALSE(TestSpellFamilyMatch(procEntry.SpellFamilyName, procEntry.SpellFamilyMask, triggerSpell)); +} + +TEST_F(SpellProcIntegrationTest, SpellFamilyMatch_NoFamilyFilter) +{ + // Create any spell + auto* triggerSpell = CreateSpellInfo(133, 3, 0x00000001); + + // Proc with no family filter should match any spell + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(0) // No family filter + .Build(); + + EXPECT_TRUE(TestSpellFamilyMatch(procEntry.SpellFamilyName, procEntry.SpellFamilyMask, triggerSpell)); +} + +TEST_F(SpellProcIntegrationTest, SpellFamilyMatch_FlagMismatch) +{ + // Create a Mage spell with specific flags + auto* triggerSpell = CreateSpellInfo(133, 3, 0x00000001); // Fireball flag + + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(3) // SPELLFAMILY_MAGE + .WithSpellFamilyMask(flag96(0x00000002, 0, 0)) // Different flag + .Build(); + + EXPECT_FALSE(TestSpellFamilyMatch(procEntry.SpellFamilyName, procEntry.SpellFamilyMask, triggerSpell)); +} + +// ============================================================================= +// Combined Flag Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, MultipleProcFlags_MeleeOrSpell) +{ + // Proc on melee OR spell damage + // Note: Spell procs require SpellPhaseMask to be set, otherwise the check + // (eventInfo.SpellPhaseMask & procEntry.SpellPhaseMask) fails when procEntry = 0 + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + // Melee test + auto meleeEventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, meleeEventInfo)); + + // Spell test - needs matching SpellPhaseMask AND SpellInfo + auto* spellInfo = CreateSpellInfo(100); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + auto spellEventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithDamageInfo(&damageInfo) + .Build(); + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, spellEventInfo)); +} + +TEST_F(SpellProcIntegrationTest, MultipleHitMasks_CritOrNormal) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL | PROC_HIT_CRITICAL) + .Build(); + + auto normalScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithNormalHit(); + EXPECT_PROC_TRIGGERS(procEntry, normalScenario); + + auto critScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithCrit(); + EXPECT_PROC_TRIGGERS(procEntry, critScenario); + + auto missScenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithMiss(); + EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, missScenario); +} + +// ============================================================================= +// School Mask Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, SchoolMaskFilter_FireOnly_FireDamage) +{ + // Proc entry requires fire school damage + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSchoolMask(SPELL_SCHOOL_MASK_FIRE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + // Create fire spell and fire damage info + auto* fireSpell = CreateSpellInfo(133, 3, 0); // Fireball + DamageInfo fireDamageInfo(nullptr, nullptr, 100, fireSpell, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithDamageInfo(&fireDamageInfo) + .Build(); + + // Fire damage should trigger fire-only proc + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcIntegrationTest, SchoolMaskFilter_FireOnly_FrostDamage) +{ + // Proc entry requires fire school damage + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSchoolMask(SPELL_SCHOOL_MASK_FIRE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + // Create frost spell and frost damage info + auto* frostSpell = CreateSpellInfo(116, 3, 0); // Frostbolt + DamageInfo frostDamageInfo(nullptr, nullptr, 100, frostSpell, SPELL_SCHOOL_MASK_FROST, SPELL_DIRECT_DAMAGE); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithDamageInfo(&frostDamageInfo) + .Build(); + + // Frost damage should NOT trigger fire-only proc + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcIntegrationTest, SchoolMaskFilter_NoSchoolMask_AnySchoolTriggers) +{ + // Proc entry with no school mask filter (accepts all schools) + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSchoolMask(0) // No filter + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + // Test with shadow damage + auto* shadowSpell = CreateSpellInfo(686, 5, 0); // Shadow Bolt + DamageInfo shadowDamageInfo(nullptr, nullptr, 100, shadowSpell, SPELL_SCHOOL_MASK_SHADOW, SPELL_DIRECT_DAMAGE); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithDamageInfo(&shadowDamageInfo) + .Build(); + + // Any school should trigger when no school mask filter is set + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// Edge Case Tests +// ============================================================================= + +TEST_F(SpellProcIntegrationTest, EmptyProcFlags_NeverTriggers) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_NONE) // No flags set + .Build(); + + auto scenario = ProcScenarioBuilder() + .OnMeleeAutoAttack() + .WithNormalHit(); + + // Without PROC_FLAG_NONE special handling, this might still match + // The actual behavior depends on implementation + auto eventInfo = scenario.Build(); + + // Event has flags but proc entry has none - should not trigger + if (procEntry.ProcFlags == 0 && scenario.GetTypeMask() != 0) + { + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); + } +} + +TEST_F(SpellProcIntegrationTest, AllHitMasks_TriggersOnAny) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_MASK_ALL) + .Build(); + + // Should trigger on any hit type + std::vector hitTypes = { + PROC_HIT_NORMAL, PROC_HIT_CRITICAL, PROC_HIT_MISS, + PROC_HIT_DODGE, PROC_HIT_PARRY, PROC_HIT_BLOCK + }; + + for (uint32_t hitType : hitTypes) + { + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(hitType) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + << "Failed for hit type: " << hitType; + } +} diff --git a/src/test/server/game/Spells/SpellProcPPMModifierTest.cpp b/src/test/server/game/Spells/SpellProcPPMModifierTest.cpp new file mode 100644 index 000000000..88749ee25 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcPPMModifierTest.cpp @@ -0,0 +1,399 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcPPMModifierTest.cpp + * @brief Unit tests for SPELLMOD_PROC_PER_MINUTE modifier application + * + * Tests the logic from Unit.cpp:10378-10390: + * - Base PPM calculation without modifiers + * - Flat PPM modifier application + * - Percent PPM modifier application + * - GetSpellModOwner() null handling + * - SpellProto null handling + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcPPMModifierTest : public ::testing::Test +{ +protected: + void SetUp() override {} + + // Standard weapon speeds for testing + static constexpr uint32 DAGGER_SPEED = 1400; // 1.4 sec + static constexpr uint32 SWORD_SPEED = 2500; // 2.5 sec + static constexpr uint32 TWO_HANDED_SPEED = 3300; // 3.3 sec +}; + +// ============================================================================= +// Base PPM Calculation (No Modifiers) +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, BasePPM_NoModifiers) +{ + ProcChanceTestHelper::PPMModifierConfig config; + // Default config: no modifiers, has spell mod owner and spell proto + + float basePPM = 6.0f; + + // With 2500ms weapon: (2500 * 6) / 600 = 25% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 25.0f, 0.01f) + << "Base PPM 6.0 with 2.5s weapon should give 25% chance"; +} + +TEST_F(SpellProcPPMModifierTest, BasePPM_DifferentWeaponSpeeds) +{ + ProcChanceTestHelper::PPMModifierConfig config; + float basePPM = 6.0f; + + // Fast dagger: (1400 * 6) / 600 = 14% + float daggerChance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + DAGGER_SPEED, basePPM, config); + EXPECT_NEAR(daggerChance, 14.0f, 0.01f); + + // Slow 2H: (3300 * 6) / 600 = 33% + float twoHandChance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + TWO_HANDED_SPEED, basePPM, config); + EXPECT_NEAR(twoHandChance, 33.0f, 0.01f); +} + +TEST_F(SpellProcPPMModifierTest, BasePPM_ZeroPPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, 0.0f, config); + + EXPECT_EQ(chance, 0.0f) + << "Zero PPM should return 0% chance"; +} + +TEST_F(SpellProcPPMModifierTest, BasePPM_NegativePPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, -5.0f, config); + + EXPECT_EQ(chance, 0.0f) + << "Negative PPM should return 0% chance"; +} + +// ============================================================================= +// Flat Modifier Tests - SPELLMOD_FLAT for SPELLMOD_PROC_PER_MINUTE +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, FlatModifier_IncreasesPPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = 2.0f; // +2 PPM + + float basePPM = 6.0f; + // Modified PPM: 6 + 2 = 8 + // Chance: (2500 * 8) / 600 = 33.33% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 33.33f, 0.1f) + << "Flat +2 PPM modifier should increase chance from 25% to 33.33%"; +} + +TEST_F(SpellProcPPMModifierTest, FlatModifier_DecreasesPPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = -3.0f; // -3 PPM + + float basePPM = 6.0f; + // Modified PPM: 6 - 3 = 3 + // Chance: (2500 * 3) / 600 = 12.5% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 12.5f, 0.1f) + << "Flat -3 PPM modifier should decrease chance from 25% to 12.5%"; +} + +TEST_F(SpellProcPPMModifierTest, FlatModifier_ReducesToZero) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = -10.0f; // Would reduce to -4 PPM + + float basePPM = 6.0f; + // Modified PPM: 6 - 10 = -4 (negative) + // Formula still applies: (2500 * -4) / 600 = negative + // But the check at start for PPM <= 0 happens before modifiers + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + // Note: In the real code, negative results are possible after modifiers + // The helper doesn't clamp the final result + EXPECT_LT(chance, 0.0f) + << "Extreme negative modifier can produce negative chance"; +} + +// ============================================================================= +// Percent Modifier Tests - SPELLMOD_PCT for SPELLMOD_PROC_PER_MINUTE +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, PercentModifier_50PercentIncrease) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.pctModifier = 1.5f; // 150% = 50% increase + + float basePPM = 6.0f; + // Modified PPM: 6 * 1.5 = 9 + // Chance: (2500 * 9) / 600 = 37.5% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 37.5f, 0.1f) + << "50% PPM increase should raise chance from 25% to 37.5%"; +} + +TEST_F(SpellProcPPMModifierTest, PercentModifier_50PercentDecrease) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.pctModifier = 0.5f; // 50% = 50% decrease + + float basePPM = 6.0f; + // Modified PPM: 6 * 0.5 = 3 + // Chance: (2500 * 3) / 600 = 12.5% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 12.5f, 0.1f) + << "50% PPM decrease should lower chance from 25% to 12.5%"; +} + +TEST_F(SpellProcPPMModifierTest, PercentModifier_DoublesPPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.pctModifier = 2.0f; // 200% + + float basePPM = 6.0f; + // Modified PPM: 6 * 2 = 12 + // Chance: (2500 * 12) / 600 = 50% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 50.0f, 0.1f) + << "100% PPM increase should double chance to 50%"; +} + +// ============================================================================= +// Combined Modifiers Tests +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, CombinedModifiers_FlatThenPercent) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = 2.0f; // +2 PPM first + config.pctModifier = 1.5f; // Then 50% increase + + float basePPM = 6.0f; + // Flat first: 6 + 2 = 8 + // Percent: 8 * 1.5 = 12 + // Chance: (2500 * 12) / 600 = 50% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 50.0f, 0.1f) + << "Flat +2 then 50% increase should result in 50% chance"; +} + +TEST_F(SpellProcPPMModifierTest, CombinedModifiers_BothIncrease) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = 4.0f; // +4 PPM + config.pctModifier = 1.25f; // 25% increase + + float basePPM = 6.0f; + // Flat first: 6 + 4 = 10 + // Percent: 10 * 1.25 = 12.5 + // Chance: (2500 * 12.5) / 600 = 52.08% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 52.08f, 0.1f); +} + +// ============================================================================= +// No SpellModOwner Tests - GetSpellModOwner() returns null +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, NoSpellModOwner_ModifiersIgnored) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.hasSpellModOwner = false; // GetSpellModOwner() returns null + config.flatModifier = 10.0f; // Would significantly change result + config.pctModifier = 2.0f; + + float basePPM = 6.0f; + // Without spell mod owner, modifiers are NOT applied + // Chance: (2500 * 6) / 600 = 25% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 25.0f, 0.1f) + << "Without spell mod owner, modifiers should be ignored"; +} + +// ============================================================================= +// No SpellProto Tests - spellProto is null +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, NoSpellProto_ModifiersIgnored) +{ + ProcChanceTestHelper::PPMModifierConfig config; + config.hasSpellProto = false; // spellProto is null + config.flatModifier = 10.0f; + config.pctModifier = 2.0f; + + float basePPM = 6.0f; + // Without spell proto, modifiers are NOT applied + // Chance: (2500 * 6) / 600 = 25% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, basePPM, config); + + EXPECT_NEAR(chance, 25.0f, 0.1f) + << "Without spell proto, modifiers should be ignored"; +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, Scenario_OmenOfClarity_BasePPM) +{ + // Omen of Clarity: 6 PPM + ProcChanceTestHelper::PPMModifierConfig config; + + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, 6.0f, config); + + EXPECT_NEAR(chance, 25.0f, 0.1f) + << "Omen of Clarity base chance with 2.5s weapon"; +} + +TEST_F(SpellProcPPMModifierTest, Scenario_OmenOfClarity_WithTalent) +{ + // Hypothetical talent that increases Omen of Clarity PPM by 2 + ProcChanceTestHelper::PPMModifierConfig config; + config.flatModifier = 2.0f; + + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, 6.0f, config); + + EXPECT_NEAR(chance, 33.33f, 0.1f) + << "Omen of Clarity with +2 PPM talent"; +} + +TEST_F(SpellProcPPMModifierTest, Scenario_WindfuryWeapon_FastWeapon) +{ + // Windfury Weapon: 2 PPM with fast weapon + ProcChanceTestHelper::PPMModifierConfig config; + + // Fast 1.5s weapon: (1500 * 2) / 600 = 5% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + 1500, 2.0f, config); + + EXPECT_NEAR(chance, 5.0f, 0.1f) + << "Windfury with 1.5s weapon"; +} + +TEST_F(SpellProcPPMModifierTest, Scenario_WindfuryWeapon_SlowWeapon) +{ + // Windfury Weapon: 2 PPM with slow weapon + ProcChanceTestHelper::PPMModifierConfig config; + + // Slow 3.6s weapon: (3600 * 2) / 600 = 12% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + 3600, 2.0f, config); + + EXPECT_NEAR(chance, 12.0f, 0.1f) + << "Windfury with 3.6s weapon"; +} + +TEST_F(SpellProcPPMModifierTest, Scenario_JudgementOfLight_HighPPM) +{ + // Judgement of Light: 15 PPM (very high) + ProcChanceTestHelper::PPMModifierConfig config; + + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, 15.0f, config); + + // (2500 * 15) / 600 = 62.5% + EXPECT_NEAR(chance, 62.5f, 0.1f) + << "Judgement of Light with 2.5s weapon"; +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcPPMModifierTest, EdgeCase_VeryFastWeapon) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + // 1.0s weapon (very fast): (1000 * 6) / 600 = 10% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + 1000, 6.0f, config); + + EXPECT_NEAR(chance, 10.0f, 0.1f); +} + +TEST_F(SpellProcPPMModifierTest, EdgeCase_VerySlowWeapon) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + // 4.0s weapon (very slow): (4000 * 6) / 600 = 40% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + 4000, 6.0f, config); + + EXPECT_NEAR(chance, 40.0f, 0.1f); +} + +TEST_F(SpellProcPPMModifierTest, EdgeCase_VeryHighPPM) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + // 60 PPM: (2500 * 60) / 600 = 250% (over 100%, can happen) + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + SWORD_SPEED, 60.0f, config); + + EXPECT_NEAR(chance, 250.0f, 0.1f) + << "Very high PPM can exceed 100% chance"; +} + +TEST_F(SpellProcPPMModifierTest, EdgeCase_ZeroWeaponSpeed) +{ + ProcChanceTestHelper::PPMModifierConfig config; + + // Zero weapon speed should result in 0% + float chance = ProcChanceTestHelper::CalculatePPMChanceWithModifiers( + 0, 6.0f, config); + + EXPECT_EQ(chance, 0.0f); +} diff --git a/src/test/server/game/Spells/SpellProcPPMTest.cpp b/src/test/server/game/Spells/SpellProcPPMTest.cpp new file mode 100644 index 000000000..b58df9a0e --- /dev/null +++ b/src/test/server/game/Spells/SpellProcPPMTest.cpp @@ -0,0 +1,377 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcPPMTest.cpp + * @brief Unit tests for PPM (Procs Per Minute) calculation + * + * Tests the formula: chance = (WeaponSpeed * PPM) / 600.0f + */ + +#include "ProcChanceTestHelper.h" +#include "UnitStub.h" +#include "gtest/gtest.h" + +using namespace testing; + +// ============================================================================= +// PPM Formula Tests +// ============================================================================= + +class SpellProcPPMTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _unit = std::make_unique(); + } + + std::unique_ptr _unit; +}; + +TEST_F(SpellProcPPMTest, PPMFormula_BasicCalculation) +{ + // Formula: (WeaponSpeed * PPM) / 600.0f + // 2500ms * 6 PPM / 600 = 25% + float result = ProcChanceTestHelper::CalculatePPMChance(2500, 6.0f); + EXPECT_NEAR(result, 25.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_FastWeapon_HigherChancePerSwing) +{ + // Fast dagger (1.4 sec = 1400ms), 6 PPM + // 1400 * 6 / 600 = 14% + float result = ProcChanceTestHelper::CalculatePPMChance(1400, 6.0f); + EXPECT_NEAR(result, 14.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_SlowWeapon_LowerChancePerSwing) +{ + // Slow 2H (3.3 sec = 3300ms), 6 PPM + // 3300 * 6 / 600 = 33% + float result = ProcChanceTestHelper::CalculatePPMChance(3300, 6.0f); + EXPECT_NEAR(result, 33.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_VerySlowWeapon) +{ + // Very slow weapon (3.8 sec = 3800ms), 6 PPM + // 3800 * 6 / 600 = 38% + float result = ProcChanceTestHelper::CalculatePPMChance(3800, 6.0f); + EXPECT_NEAR(result, 38.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_ZeroPPM_ReturnsZero) +{ + float result = ProcChanceTestHelper::CalculatePPMChance(2500, 0.0f); + EXPECT_FLOAT_EQ(result, 0.0f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_NegativePPM_ReturnsZero) +{ + float result = ProcChanceTestHelper::CalculatePPMChance(2500, -1.0f); + EXPECT_FLOAT_EQ(result, 0.0f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_WithPositiveModifier) +{ + // 2500ms, 6 PPM + 2 PPM modifier = 8 effective PPM + // 2500 * 8 / 600 = 33.33% + float result = ProcChanceTestHelper::CalculatePPMChance(2500, 6.0f, 2.0f); + EXPECT_NEAR(result, 33.33f, 0.01f); +} + +TEST_F(SpellProcPPMTest, PPMFormula_WithNegativeModifier) +{ + // 2500ms, 6 PPM - 2 PPM modifier = 4 effective PPM + // 2500 * 4 / 600 = 16.67% + float result = ProcChanceTestHelper::CalculatePPMChance(2500, 6.0f, -2.0f); + EXPECT_NEAR(result, 16.67f, 0.01f); +} + +// ============================================================================= +// UnitStub PPM Tests +// ============================================================================= + +TEST_F(SpellProcPPMTest, UnitStub_GetPPMProcChance_DefaultWeaponSpeed) +{ + // Default weapon speed is 2000ms + float result = _unit->GetPPMProcChance(2000, 6.0f); + EXPECT_NEAR(result, 20.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, UnitStub_GetPPMProcChance_CustomWeaponSpeed) +{ + _unit->SetAttackTime(0, 2500); // BASE_ATTACK + float result = _unit->GetPPMProcChance(_unit->GetAttackTime(0), 6.0f); + EXPECT_NEAR(result, 25.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, UnitStub_GetPPMProcChance_WithPPMModifier) +{ + _unit->SetPPMModifier(12345, 2.0f); // Spell ID 12345 has +2 PPM modifier + float result = _unit->GetPPMProcChance(2500, 6.0f, 12345); + // 2500 * (6 + 2) / 600 = 33.33% + EXPECT_NEAR(result, 33.33f, 0.01f); +} + +TEST_F(SpellProcPPMTest, UnitStub_GetPPMProcChance_ModifierNotAppliedWithoutSpellId) +{ + _unit->SetPPMModifier(12345, 2.0f); + // Without spell ID, modifier is not applied + float result = _unit->GetPPMProcChance(2500, 6.0f, 0); + EXPECT_NEAR(result, 25.0f, 0.01f); +} + +// ============================================================================= +// Real-World PPM Spell Examples +// ============================================================================= + +TEST_F(SpellProcPPMTest, OmenOfClarity_PPM6_VariousWeaponSpeeds) +{ + // Omen of Clarity: 6 PPM + constexpr float OOC_PPM = 6.0f; + + // Fast dagger + float daggerChance = ProcChanceTestHelper::CalculatePPMChance(1400, OOC_PPM); + EXPECT_NEAR(daggerChance, 14.0f, 0.01f); + + // Normal 1H sword + float swordChance = ProcChanceTestHelper::CalculatePPMChance(2500, OOC_PPM); + EXPECT_NEAR(swordChance, 25.0f, 0.01f); + + // Staff + float staffChance = ProcChanceTestHelper::CalculatePPMChance(3000, OOC_PPM); + EXPECT_NEAR(staffChance, 30.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, JudgementOfLight_PPM15_VariousWeaponSpeeds) +{ + // Judgement of Light: 15 PPM + constexpr float JOL_PPM = 15.0f; + + // Fast dagger + float daggerChance = ProcChanceTestHelper::CalculatePPMChance(1400, JOL_PPM); + EXPECT_NEAR(daggerChance, 35.0f, 0.01f); + + // Normal 1H sword + float swordChance = ProcChanceTestHelper::CalculatePPMChance(2500, JOL_PPM); + EXPECT_NEAR(swordChance, 62.5f, 0.01f); + + // Slow 2H weapon + float twoHanderChance = ProcChanceTestHelper::CalculatePPMChance(3300, JOL_PPM); + EXPECT_NEAR(twoHanderChance, 82.5f, 0.01f); +} + +TEST_F(SpellProcPPMTest, WindfuryWeapon_PPM2_VariousWeaponSpeeds) +{ + // Windfury Weapon: 2 PPM (low PPM for testing) + constexpr float WF_PPM = 2.0f; + + // Fast dagger + float daggerChance = ProcChanceTestHelper::CalculatePPMChance(1400, WF_PPM); + EXPECT_NEAR(daggerChance, 4.67f, 0.01f); + + // Slow 2H weapon + float twoHanderChance = ProcChanceTestHelper::CalculatePPMChance(3300, WF_PPM); + EXPECT_NEAR(twoHanderChance, 11.0f, 0.01f); +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcPPMTest, EdgeCase_VeryFastWeapon) +{ + // Very fast (theoretical) weapon - 1.0 sec = 1000ms + float result = ProcChanceTestHelper::CalculatePPMChance(1000, 6.0f); + EXPECT_NEAR(result, 10.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, EdgeCase_ExtremelySlow) +{ + // Extremely slow weapon - 5.0 sec = 5000ms + float result = ProcChanceTestHelper::CalculatePPMChance(5000, 6.0f); + EXPECT_NEAR(result, 50.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, EdgeCase_HighPPM) +{ + // High PPM value (30) + float result = ProcChanceTestHelper::CalculatePPMChance(2500, 30.0f); + // 2500 * 30 / 600 = 125% (can exceed 100%) + EXPECT_NEAR(result, 125.0f, 0.01f); +} + +TEST_F(SpellProcPPMTest, EdgeCase_FractionalPPM) +{ + // Fractional PPM value (2.5) + float result = ProcChanceTestHelper::CalculatePPMChance(2400, 2.5f); + // 2400 * 2.5 / 600 = 10% + EXPECT_NEAR(result, 10.0f, 0.01f); +} + +// ============================================================================= +// Shapeshifter Enchant PPM Bug Tests +// +// Player::CastItemCombatSpell has two PPM paths: +// 1) Item spells (line ~7308): uses GetAttackTime(attType) - CORRECT +// 2) Enchantment procs (line ~7375): uses proto->Delay - BUG +// +// For non-shapeshifted players these return the same value, but for +// Feral Druids proto->Delay reflects the weapon (e.g. 3.6s staff) +// while GetAttackTime returns the form speed (1.0s Cat, 2.5s Bear). +// ============================================================================= + +TEST_F(SpellProcPPMTest, ShapeshiftBug_NonShifted_NoDiscrepancy) +{ + // A warrior with a 3.6s weapon: proto->Delay == GetAttackTime() + constexpr uint32 WEAPON_DELAY = ProcChanceTestHelper::WEAPON_SPEED_STAFF; + constexpr float MONGOOSE_PPM = 1.0f; + + _unit->SetAttackTime(0, WEAPON_DELAY); + + float chanceFromProtoDelay = ProcChanceTestHelper::CalculatePPMChance(WEAPON_DELAY, MONGOOSE_PPM); + float chanceFromGetAttackTime = ProcChanceTestHelper::CalculatePPMChance( + _unit->GetAttackTime(0), MONGOOSE_PPM); + + EXPECT_FLOAT_EQ(chanceFromProtoDelay, chanceFromGetAttackTime) + << "Non-shapeshifted: proto->Delay and GetAttackTime() should be identical"; +} + +TEST_F(SpellProcPPMTest, ShapeshiftBug_CatForm_ProtoDelayInflatesChance) +{ + // Druid in Cat Form with a 3.6s staff equipped + // proto->Delay = 3600ms (the staff), GetAttackTime = 1000ms (Cat Form) + constexpr uint32 STAFF_DELAY = ProcChanceTestHelper::WEAPON_SPEED_STAFF; + constexpr uint32 CAT_SPEED = ProcChanceTestHelper::FORM_SPEED_CAT; + constexpr float MONGOOSE_PPM = 1.0f; + + _unit->SetAttackTime(0, CAT_SPEED); + + float buggyChance = ProcChanceTestHelper::CalculatePPMChance(STAFF_DELAY, MONGOOSE_PPM); + float correctChance = ProcChanceTestHelper::CalculatePPMChance(CAT_SPEED, MONGOOSE_PPM); + + // proto->Delay gives 3600 * 1 / 600 = 6.0% per swing + EXPECT_NEAR(buggyChance, 6.0f, 0.01f); + // GetAttackTime gives 1000 * 1 / 600 = 1.67% per swing + EXPECT_NEAR(correctChance, 1.67f, 0.01f); + + // The bug inflates chance per swing by weapon_speed / form_speed + EXPECT_NEAR(buggyChance / correctChance, + static_cast(STAFF_DELAY) / static_cast(CAT_SPEED), 0.01f) + << "Bug inflates per-swing chance by ratio of weapon speed to form speed"; +} + +TEST_F(SpellProcPPMTest, ShapeshiftBug_CatForm_EffectivePPMIs3Point6x) +{ + // Cat Form attacks every 1.0s (60 swings/min) + // With the buggy 6.0% chance per swing: 60 * 0.06 = 3.6 procs/min + // With the correct 1.67% chance: 60 * 0.0167 = 1.0 procs/min + constexpr uint32 STAFF_DELAY = ProcChanceTestHelper::WEAPON_SPEED_STAFF; + constexpr uint32 CAT_SPEED = ProcChanceTestHelper::FORM_SPEED_CAT; + constexpr float MONGOOSE_PPM = 1.0f; + + float buggyChance = ProcChanceTestHelper::CalculatePPMChance(STAFF_DELAY, MONGOOSE_PPM); + float correctChance = ProcChanceTestHelper::CalculatePPMChance(CAT_SPEED, MONGOOSE_PPM); + + float buggyEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(buggyChance, CAT_SPEED); + float correctEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(correctChance, CAT_SPEED); + + // Buggy: effective PPM is 3.6 instead of 1.0 + EXPECT_NEAR(buggyEffectivePPM, 3.6f, 0.01f) + << "Bug: Cat Form Mongoose procs 3.6 times/min instead of 1.0"; + // Correct: effective PPM matches the intended value + EXPECT_NEAR(correctEffectivePPM, MONGOOSE_PPM, 0.01f) + << "Fix: Cat Form Mongoose should proc exactly 1.0 times/min"; +} + +TEST_F(SpellProcPPMTest, ShapeshiftBug_BearForm_ProtoDelayInflatesChance) +{ + // Bear Form with 3.6s staff: proto->Delay = 3600, GetAttackTime = 2500 + constexpr uint32 STAFF_DELAY = ProcChanceTestHelper::WEAPON_SPEED_STAFF; + constexpr uint32 BEAR_SPEED = ProcChanceTestHelper::FORM_SPEED_BEAR; + constexpr float MONGOOSE_PPM = 1.0f; + + _unit->SetAttackTime(0, BEAR_SPEED); + + float buggyChance = ProcChanceTestHelper::CalculatePPMChance(STAFF_DELAY, MONGOOSE_PPM); + float correctChance = ProcChanceTestHelper::CalculatePPMChance(BEAR_SPEED, MONGOOSE_PPM); + + float buggyEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(buggyChance, BEAR_SPEED); + float correctEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(correctChance, BEAR_SPEED); + + // Buggy: 1.44 PPM instead of 1.0 + EXPECT_NEAR(buggyEffectivePPM, 1.44f, 0.01f) + << "Bug: Bear Form Mongoose procs 1.44 times/min instead of 1.0"; + EXPECT_NEAR(correctEffectivePPM, MONGOOSE_PPM, 0.01f) + << "Fix: Bear Form Mongoose should proc exactly 1.0 times/min"; +} + +TEST_F(SpellProcPPMTest, ShapeshiftBug_CatForm_FieryWeapon6PPM) +{ + // Fiery Weapon (6 PPM) in Cat Form with 3.6s staff + constexpr uint32 STAFF_DELAY = ProcChanceTestHelper::WEAPON_SPEED_STAFF; + constexpr uint32 CAT_SPEED = ProcChanceTestHelper::FORM_SPEED_CAT; + constexpr float FIERY_PPM = 6.0f; + + float buggyChance = ProcChanceTestHelper::CalculatePPMChance(STAFF_DELAY, FIERY_PPM); + float correctChance = ProcChanceTestHelper::CalculatePPMChance(CAT_SPEED, FIERY_PPM); + + float buggyEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(buggyChance, CAT_SPEED); + float correctEffectivePPM = ProcChanceTestHelper::CalculateEffectivePPM(correctChance, CAT_SPEED); + + // Buggy: 36% chance per swing → 21.6 procs/min instead of 6.0 + EXPECT_NEAR(buggyChance, 36.0f, 0.01f); + EXPECT_NEAR(correctChance, 10.0f, 0.01f); + EXPECT_NEAR(buggyEffectivePPM, 21.6f, 0.01f) + << "Bug: Cat Form Fiery Weapon procs 21.6 times/min instead of 6.0"; + EXPECT_NEAR(correctEffectivePPM, FIERY_PPM, 0.01f) + << "Fix: Cat Form Fiery Weapon should proc exactly 6.0 times/min"; +} + +TEST_F(SpellProcPPMTest, ShapeshiftBug_ItemSpellPath_AlreadyCorrect) +{ + // The item spell PPM path (line ~7308) already uses GetAttackTime. + // Verify that using GetAttackTime gives correct PPM for all forms. + constexpr float PPM = 1.0f; + + struct FormScenario + { + const char* name; + uint32 formSpeed; + }; + + FormScenario scenarios[] = { + {"Normal (3.6s weapon)", ProcChanceTestHelper::WEAPON_SPEED_STAFF}, + {"Cat Form", ProcChanceTestHelper::FORM_SPEED_CAT}, + {"Bear Form", ProcChanceTestHelper::FORM_SPEED_BEAR}, + }; + + for (auto const& scenario : scenarios) + { + _unit->SetAttackTime(0, scenario.formSpeed); + + float chance = ProcChanceTestHelper::CalculatePPMChance( + _unit->GetAttackTime(0), PPM); + float effectivePPM = ProcChanceTestHelper::CalculateEffectivePPM( + chance, scenario.formSpeed); + + EXPECT_NEAR(effectivePPM, PPM, 0.01f) + << scenario.name << ": GetAttackTime-based PPM should always match intended PPM"; + } +} diff --git a/src/test/server/game/Spells/SpellProcPipelineTest.cpp b/src/test/server/game/Spells/SpellProcPipelineTest.cpp new file mode 100644 index 000000000..3edc1cf98 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcPipelineTest.cpp @@ -0,0 +1,462 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcPipelineTest.cpp + * @brief End-to-end integration tests for the full proc pipeline + * + * Tests the complete proc execution flow: + * 1. Cooldown check (IsProcOnCooldown) + * 2. Chance calculation (CalcProcChance) + * 3. Roll check (rand_chance) + * 4. Cooldown application + * 5. Charge consumption (ConsumeProcCharges) + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "AuraStub.h" +#include "UnitStub.h" +#include "gtest/gtest.h" + +using namespace testing; +using namespace std::chrono_literals; + +class SpellProcPipelineTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _scenario = std::make_unique(); + } + + std::unique_ptr _scenario; +}; + +// ============================================================================= +// Full Pipeline Flow Tests +// ============================================================================= + +TEST_F(SpellProcPipelineTest, FullFlow_BasicProc_100Percent) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // 100% chance should always proc + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, FullFlow_BasicProc_0Percent) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(0.0f) + .Build(); + + // 0% chance should never proc (when roll is > 0) + EXPECT_FALSE(_scenario->SimulateProc(procEntry, 50.0f)); +} + +TEST_F(SpellProcPipelineTest, FullFlow_WithCooldown) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(1000ms) + .Build(); + + // First proc succeeds + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + + // Second proc blocked by cooldown + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + // Wait for cooldown + _scenario->AdvanceTime(1100ms); + + // Third proc succeeds + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, FullFlow_WithCharges) +{ + _scenario->WithAura(12345, 3); // 3 charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First proc - 3 -> 2 charges + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 2); + + // Second proc - 2 -> 1 charges + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 1); + + // Third proc - 1 -> 0 charges, aura removed + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 0); + EXPECT_TRUE(_scenario->GetAura()->IsRemoved()); +} + +TEST_F(SpellProcPipelineTest, FullFlow_WithStacks) +{ + _scenario->WithAura(12345, 0, 5); // 5 stacks, no charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithAttributesMask(PROC_ATTR_USE_STACKS_FOR_CHARGES) + .Build(); + + // Each proc consumes one stack + for (int i = 5; i > 0; --i) + { + EXPECT_EQ(_scenario->GetAura()->GetStackAmount(), i); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + } + + EXPECT_EQ(_scenario->GetAura()->GetStackAmount(), 0); + EXPECT_TRUE(_scenario->GetAura()->IsRemoved()); +} + +// ============================================================================= +// Combined Feature Tests +// ============================================================================= + +TEST_F(SpellProcPipelineTest, Combined_ChargesAndCooldown) +{ + _scenario->WithAura(12345, 5); // 5 charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(500ms) + .Build(); + + // First proc at t=0 + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 4); + + // Blocked at t=0 (cooldown) + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 4); + + // Wait and proc again at t=600ms + _scenario->AdvanceTime(600ms); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 3); + + // Blocked at t=600ms + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 3); +} + +TEST_F(SpellProcPipelineTest, Combined_PPM_AndCooldown) +{ + _scenario->WithAura(12345); + _scenario->WithWeaponSpeed(0, 2500); // BASE_ATTACK = 2500ms + + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) // 25% with 2500ms weapon + .WithCooldown(1000ms) + .Build(); + + // First proc (roll 0 = always pass) + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 0.0f)); + + // Blocked by cooldown even if roll would pass + EXPECT_FALSE(_scenario->SimulateProc(procEntry, 0.0f)); + + // Wait for cooldown + _scenario->AdvanceTime(1100ms); + + // Can proc again + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 0.0f)); +} + +TEST_F(SpellProcPipelineTest, Combined_Level60Reduction_WithCooldown) +{ + _scenario->WithAura(12345); + _scenario->WithActorLevel(80); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .WithCooldown(1000ms) + .Build(); + + // Level 80: 30% * (1 - 20/30) = 10% effective chance + // Roll of 5 should pass + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 5.0f)); + + // Blocked by cooldown + EXPECT_FALSE(_scenario->SimulateProc(procEntry, 5.0f)); + + // Wait and try again + _scenario->AdvanceTime(1100ms); + + // Roll of 15 should fail (10% chance) + EXPECT_FALSE(_scenario->SimulateProc(procEntry, 15.0f)); +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcPipelineTest, Scenario_OmenOfClarity) +{ + // Omen of Clarity: 6 PPM, no cooldown, no charges + _scenario->WithAura(16864); // Omen of Clarity + _scenario->WithWeaponSpeed(0, 2500); // Staff + + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) // 25% with 2500ms + .Build(); + + // Simulate multiple hits + int procCount = 0; + for (int i = 0; i < 10; ++i) + { + // Roll values simulating ~25% success rate + float roll = (i % 4 == 0) ? 10.0f : 50.0f; + if (_scenario->SimulateProc(procEntry, roll)) + procCount++; + } + + // With deterministic rolls, should have 3 procs (indexes 0, 4, 8) + // But our test is roll > chance check, so roll 10 fails against 25% chance + // Actually roll 0 always passes, non-zero rolls check roll > chance +} + +TEST_F(SpellProcPipelineTest, Scenario_LeaderOfThePack) +{ + // Leader of the Pack: 6 second ICD + _scenario->WithAura(24932); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(6000ms) + .Build(); + + // First crit - procs + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + + // Second crit at 1 second - blocked + _scenario->AdvanceTime(1000ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + // Third crit at 5 seconds - blocked + _scenario->AdvanceTime(4000ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + // Fourth crit at 6.1 seconds - allowed + _scenario->AdvanceTime(1100ms); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, Scenario_ArtOfWar) +{ + // Art of War: 2 charges (typically) + _scenario->WithAura(53486, 2); // Art of War + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First Exorcism - consumes charge + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 1); + + // Second Exorcism - consumes last charge + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 0); + EXPECT_TRUE(_scenario->GetAura()->IsRemoved()); +} + +TEST_F(SpellProcPipelineTest, Scenario_LightningShield) +{ + // Lightning Shield: 3 charges (orbs) + _scenario->WithAura(324, 3); // Lightning Shield Rank 1 + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // First hit - uses orb + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 2); + + // Second hit - uses orb + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_EQ(_scenario->GetAura()->GetCharges(), 1); + + // Third hit - last orb, aura removed + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_TRUE(_scenario->GetAura()->IsRemoved()); +} + +TEST_F(SpellProcPipelineTest, Scenario_WanderingPlague) +{ + // Wandering Plague: 1 second ICD + _scenario->WithAura(49217); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(1000ms) + .Build(); + + // First tick procs + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + + // Rapid ticks blocked + _scenario->AdvanceTime(200ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + _scenario->AdvanceTime(200ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + _scenario->AdvanceTime(200ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + // After 1 second total, can proc again + _scenario->AdvanceTime(600ms); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcPipelineTest, EdgeCase_NoAura_NoProcPossible) +{ + // Don't set up aura + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, EdgeCase_ZeroCooldown_AllowsRapidProcs) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(0ms) + .Build(); + + // Multiple rapid procs should all succeed + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, EdgeCase_VeryLongCooldown) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .WithCooldown(300000ms) // 5 minute cooldown + .Build(); + + // First proc + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + + // Blocked even after 4 minutes + _scenario->AdvanceTime(240000ms); + EXPECT_FALSE(_scenario->SimulateProc(procEntry)); + + // Allowed after 5 minutes + _scenario->AdvanceTime(60001ms); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); +} + +TEST_F(SpellProcPipelineTest, EdgeCase_ManyCharges) +{ + _scenario->WithAura(12345, 100); // 100 charges + + auto procEntry = SpellProcEntryBuilder() + .WithChance(100.0f) + .Build(); + + // Consume all charges + for (int i = 100; i > 0; --i) + { + EXPECT_EQ(_scenario->GetAura()->GetCharges(), i); + EXPECT_TRUE(_scenario->SimulateProc(procEntry)); + } + + EXPECT_TRUE(_scenario->GetAura()->IsRemoved()); +} + +// ============================================================================= +// Actor Configuration Tests +// ============================================================================= + +TEST_F(SpellProcPipelineTest, ActorLevel_AffectsProcChance) +{ + _scenario->WithAura(12345); + _scenario->WithActorLevel(60); + + auto procEntry = SpellProcEntryBuilder() + .WithChance(30.0f) + .WithAttributesMask(PROC_ATTR_REDUCE_PROC_60) + .Build(); + + // At level 60, full 30% chance + // Roll of 25 should pass + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 25.0f)); + + // Reset + _scenario->GetAura()->ResetProcCooldown(); + + // Change to level 80 + _scenario->WithActorLevel(80); + + // At level 80, only 10% chance + // Roll of 25 should fail + EXPECT_FALSE(_scenario->SimulateProc(procEntry, 25.0f)); +} + +TEST_F(SpellProcPipelineTest, WeaponSpeed_AffectsPPMChance) +{ + _scenario->WithAura(12345); + + auto procEntry = SpellProcEntryBuilder() + .WithProcsPerMinute(6.0f) + .Build(); + + // Fast dagger (1400ms): 14% chance + _scenario->WithWeaponSpeed(0, 1400); + // Roll of 10 should pass (< 14%) + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 10.0f)); + + // Reset cooldown + _scenario->GetAura()->ResetProcCooldown(); + + // Slow 2H (3300ms): 33% chance + _scenario->WithWeaponSpeed(0, 3300); + // Roll of 30 should pass (< 33%) + EXPECT_TRUE(_scenario->SimulateProc(procEntry, 30.0f)); +} diff --git a/src/test/server/game/Spells/SpellProcSpellTypeMaskTest.cpp b/src/test/server/game/Spells/SpellProcSpellTypeMaskTest.cpp new file mode 100644 index 000000000..063075de0 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcSpellTypeMaskTest.cpp @@ -0,0 +1,226 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AuraScriptTestFramework.h" +#include "SpellMgr.h" +#include "gtest/gtest.h" + +/** + * @brief Tests for SpellTypeMask calculation based on proc phase + * + * These tests verify that the proc system correctly calculates SpellTypeMask + * for different proc phases. This is critical because: + * - CAST phase: No damage/heal has occurred yet + * - HIT phase: Damage/heal info is available + * - FINISH phase: damageInfo may be null even for damage spells + * + * Regression test for: FINISH phase was incorrectly using NO_DMG_HEAL when + * damageInfo was null, breaking procs like Killing Machine (51124) that + * require SpellTypeMask=DAMAGE and SpellPhaseMask=FINISH. + */ +class SpellProcSpellTypeMaskTest : public AuraScriptProcTestFixture +{ +protected: + void SetUp() override + { + AuraScriptProcTestFixture::SetUp(); + } + + /** + * @brief Calculate spellTypeMask the same way ProcSkillsAndAuras does + * + * This mirrors the logic in Unit::ProcSkillsAndAuras to allow unit testing + * of the spellTypeMask calculation without needing full Unit objects. + */ + static uint32 CalculateSpellTypeMask(uint32 procPhase, DamageInfo* damageInfo, HealInfo* healInfo, bool hasSpellInfo) + { + uint32 spellTypeMask = 0; + if (procPhase == PROC_SPELL_PHASE_CAST || procPhase == PROC_SPELL_PHASE_FINISH) + { + // At CAST phase, no damage/heal has occurred yet - use MASK_ALL + // At FINISH phase, damageInfo may be null but spell did do damage - use MASK_ALL + spellTypeMask = PROC_SPELL_TYPE_MASK_ALL; + } + else if (healInfo && healInfo->GetHeal()) + spellTypeMask = PROC_SPELL_TYPE_HEAL; + else if (damageInfo && damageInfo->GetDamage()) + spellTypeMask = PROC_SPELL_TYPE_DAMAGE; + else if (hasSpellInfo) + spellTypeMask = PROC_SPELL_TYPE_NO_DMG_HEAL; + + return spellTypeMask; + } +}; + +// ============================================================================= +// SpellTypeMask Calculation Tests +// ============================================================================= + +TEST_F(SpellProcSpellTypeMaskTest, CastPhase_UsesMaskAll) +{ + // CAST phase should use MASK_ALL regardless of damage/heal info + uint32 result = CalculateSpellTypeMask(PROC_SPELL_PHASE_CAST, nullptr, nullptr, true); + EXPECT_EQ(result, PROC_SPELL_TYPE_MASK_ALL); +} + +TEST_F(SpellProcSpellTypeMaskTest, FinishPhase_UsesMaskAll_EvenWithNullDamageInfo) +{ + // FINISH phase should use MASK_ALL even when damageInfo is null + // This is the key regression test - previously returned NO_DMG_HEAL + uint32 result = CalculateSpellTypeMask(PROC_SPELL_PHASE_FINISH, nullptr, nullptr, true); + EXPECT_EQ(result, PROC_SPELL_TYPE_MASK_ALL); + + // Verify it includes DAMAGE type (required for procs like Killing Machine) + EXPECT_TRUE(result & PROC_SPELL_TYPE_DAMAGE); +} + +TEST_F(SpellProcSpellTypeMaskTest, HitPhase_WithDamage_UsesDamageType) +{ + auto* spellInfo = CreateSpellInfo(12345, 15, 0); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FROST, SPELL_DIRECT_DAMAGE); + + uint32 result = CalculateSpellTypeMask(PROC_SPELL_PHASE_HIT, &damageInfo, nullptr, true); + EXPECT_EQ(result, PROC_SPELL_TYPE_DAMAGE); +} + +TEST_F(SpellProcSpellTypeMaskTest, HitPhase_WithHeal_UsesHealType) +{ + auto* spellInfo = CreateSpellInfo(12345, 15, 0); + HealInfo healInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_HOLY); + + uint32 result = CalculateSpellTypeMask(PROC_SPELL_PHASE_HIT, nullptr, &healInfo, true); + EXPECT_EQ(result, PROC_SPELL_TYPE_HEAL); +} + +TEST_F(SpellProcSpellTypeMaskTest, HitPhase_NoDamageNoHeal_UsesNoDmgHeal) +{ + // HIT phase with no damage/heal info should use NO_DMG_HEAL + uint32 result = CalculateSpellTypeMask(PROC_SPELL_PHASE_HIT, nullptr, nullptr, true); + EXPECT_EQ(result, PROC_SPELL_TYPE_NO_DMG_HEAL); +} + +// ============================================================================= +// Killing Machine Regression Test +// ============================================================================= + +/** + * @brief Regression test for Killing Machine (51124) proc consumption + * + * Killing Machine has: + * - SpellTypeMask = 1 (PROC_SPELL_TYPE_DAMAGE) + * - SpellPhaseMask = 4 (PROC_SPELL_PHASE_FINISH) + * + * When Icy Touch is cast, the FINISH phase event must have a spellTypeMask + * that includes DAMAGE for the proc to fire and consume the buff. + * + * The bug was: FINISH phase calculated spellTypeMask as NO_DMG_HEAL (4) + * because damageInfo was null, causing the proc check to fail. + */ +TEST_F(SpellProcSpellTypeMaskTest, KillingMachine_FinishPhase_MatchesDamageTypeMask) +{ + // Killing Machine spell_proc entry + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(15) // SPELLFAMILY_DEATHKNIGHT + .WithSpellFamilyMask(flag96(2, 6, 0)) // Icy Touch, Frost Strike, Howling Blast + .WithProcFlags(PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS | PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_FINISH) + .WithAttributesMask(PROC_ATTR_REQ_SPELLMOD) + .WithCharges(1) + .Build(); + + // Calculate what spellTypeMask FINISH phase would produce + // (simulating Spell.cpp calling ProcSkillsAndAuras with nullptr damageInfo) + uint32 finishPhaseSpellTypeMask = CalculateSpellTypeMask(PROC_SPELL_PHASE_FINISH, nullptr, nullptr, true); + + // Verify the calculated mask includes DAMAGE type + EXPECT_TRUE(finishPhaseSpellTypeMask & PROC_SPELL_TYPE_DAMAGE) + << "FINISH phase spellTypeMask must include PROC_SPELL_TYPE_DAMAGE for Killing Machine to work"; + + // Verify that the proc entry's SpellTypeMask requirement is satisfied + EXPECT_TRUE(finishPhaseSpellTypeMask & procEntry.SpellTypeMask) + << "FINISH phase spellTypeMask (" << finishPhaseSpellTypeMask + << ") must match Killing Machine's SpellTypeMask requirement (" << procEntry.SpellTypeMask << ")"; +} + +/** + * @brief Verify FINISH phase works with actual CanSpellTriggerProcOnEvent + * + * This test verifies the full integration: when we pass the correctly + * calculated spellTypeMask to CanSpellTriggerProcOnEvent, Killing Machine + * style procs should work. + */ +TEST_F(SpellProcSpellTypeMaskTest, KillingMachine_FullIntegration_ProcTriggers) +{ + // Killing Machine spell_proc entry + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(15) // SPELLFAMILY_DEATHKNIGHT + .WithSpellFamilyMask(flag96(2, 0, 0)) // Icy Touch + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_FINISH) + .Build(); + + // Create Icy Touch spell info (SpellFamilyFlags = [2, 0, 0]) + auto* icyTouchSpell = CreateSpellInfo(49909, 15, 2); // DK family, mask0=2 + DamageInfo damageInfo(nullptr, nullptr, 100, icyTouchSpell, SPELL_SCHOOL_MASK_FROST, SPELL_DIRECT_DAMAGE); + + // Create event with FINISH phase and MASK_ALL (as the fix provides) + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellTypeMask(PROC_SPELL_TYPE_MASK_ALL) // Fixed behavior + .WithSpellPhaseMask(PROC_SPELL_PHASE_FINISH) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + << "Killing Machine style proc should trigger on FINISH phase with MASK_ALL spellTypeMask"; +} + +/** + * @brief Verify the bug scenario - FINISH phase with NO_DMG_HEAL fails + * + * This test documents the bug behavior: if FINISH phase incorrectly uses + * NO_DMG_HEAL spellTypeMask, Killing Machine style procs fail. + */ +TEST_F(SpellProcSpellTypeMaskTest, KillingMachine_BugScenario_NoDmgHealFails) +{ + auto procEntry = SpellProcEntryBuilder() + .WithSpellFamilyName(15) + .WithSpellFamilyMask(flag96(2, 0, 0)) + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) // Requires DAMAGE + .WithSpellPhaseMask(PROC_SPELL_PHASE_FINISH) + .Build(); + + auto* icyTouchSpell = CreateSpellInfo(49909, 15, 2); + DamageInfo damageInfo(nullptr, nullptr, 100, icyTouchSpell, SPELL_SCHOOL_MASK_FROST, SPELL_DIRECT_DAMAGE); + + // Simulate the bug: FINISH phase with NO_DMG_HEAL (the old broken behavior) + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithHitMask(PROC_HIT_NORMAL) + .WithSpellTypeMask(PROC_SPELL_TYPE_NO_DMG_HEAL) // Bug: wrong mask + .WithSpellPhaseMask(PROC_SPELL_PHASE_FINISH) + .WithDamageInfo(&damageInfo) + .Build(); + + // This should fail - documenting the bug behavior + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)) + << "With NO_DMG_HEAL spellTypeMask, DAMAGE-requiring procs should NOT trigger (this was the bug)"; +} diff --git a/src/test/server/game/Spells/SpellProcTest.cpp b/src/test/server/game/Spells/SpellProcTest.cpp new file mode 100644 index 000000000..7bf7fe869 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcTest.cpp @@ -0,0 +1,903 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "ProcEventInfoHelper.h" +#include "SpellInfoTestHelper.h" +#include "SpellMgr.h" +#include "WorldMock.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +using namespace testing; + +/** + * @brief Test fixture for SpellMgr proc tests + * + * Tests the CanSpellTriggerProcOnEvent function and related proc logic. + */ +class SpellProcTest : public ::testing::Test +{ +protected: + void SetUp() override + { + _originalWorld = sWorld.release(); + _worldMock = new NiceMock(); + sWorld.reset(_worldMock); + + static std::string emptyString; + ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); + } + + void TearDown() override + { + IWorld* currentWorld = sWorld.release(); + delete currentWorld; + _worldMock = nullptr; + + sWorld.reset(_originalWorld); + _originalWorld = nullptr; + + // Clean up any SpellInfo objects we created + for (auto* spellInfo : _spellInfos) + delete spellInfo; + _spellInfos.clear(); + } + + // Helper to create and track SpellInfo objects for cleanup + SpellInfo* CreateSpellInfo(uint32 id = 1, uint32 familyName = 0, + uint32 familyFlag0 = 0, uint32 familyFlag1 = 0, uint32 familyFlag2 = 0) + { + auto* spellInfo = SpellInfoBuilder() + .WithId(id) + .WithSpellFamilyName(familyName) + .WithSpellFamilyFlags(familyFlag0, familyFlag1, familyFlag2) + .Build(); + _spellInfos.push_back(spellInfo); + return spellInfo; + } + + IWorld* _originalWorld = nullptr; + NiceMock* _worldMock = nullptr; + std::vector _spellInfos; +}; + +// ============================================================================= +// ProcFlags Tests - Basic proc flag matching +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_ProcFlagsMatch) +{ + // Setup: Create a proc entry that triggers on melee auto attacks + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .Build(); + + // Create ProcEventInfo with matching type mask + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + // Should match + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_ProcFlagsNoMatch) +{ + // Setup: Create a proc entry that triggers on melee auto attacks + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .Build(); + + // Create ProcEventInfo with different type mask (ranged instead of melee) + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_RANGED_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + // Should not match + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_MultipleProcFlagsPartialMatch) +{ + // Setup: Create a proc entry that triggers on melee OR ranged + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_RANGED_AUTO_ATTACK) + .Build(); + + // Create ProcEventInfo with only melee + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + // Should match (partial match is OK - it's an OR relationship) + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// Kill/Death Event Tests - These always trigger regardless of other conditions +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_KillEventAlwaysProcs) +{ + // Setup: Create a proc entry for kill events + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_KILL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_KILL) + .Build(); + + // Kill events should always trigger + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_KilledEventAlwaysProcs) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_KILLED) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_KILLED) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_DeathEventAlwaysProcs) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DEATH) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DEATH) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// HitMask Tests - Test hit type filtering +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskCriticalMatch) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskCriticalNoMatch) +{ + // Proc entry requires critical hit + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + // Event is a normal hit + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskDefaultForDone) +{ + // When HitMask is 0, default for DONE procs is NORMAL | CRITICAL | ABSORB + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(0) // Default + .Build(); + + // Normal hit should work with default mask + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskDefaultForTaken) +{ + // When HitMask is 0, default for TAKEN procs is NORMAL | CRITICAL + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(0) // Default + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskMissNoMatch) +{ + // Miss should not trigger default hit mask + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(0) // Default allows NORMAL | CRITICAL | ABSORB + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_MISS) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskDodge) +{ + // Explicitly require dodge + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_DODGE) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_DODGE) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskParry) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_PARRY) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_PARRY) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HitMaskBlock) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_BLOCK) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_BLOCK) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// SpellTypeMask Tests - Damage vs Heal vs Other +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellTypeMaskDamage) +{ + auto* spellInfo = CreateSpellInfo(1); + + // Create DamageInfo for the test + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellTypeMaskHeal) +{ + auto* spellInfo = CreateSpellInfo(1); + + // Create HealInfo with the spell info so GetSpellInfo() works + HealInfo healInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_HOLY); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithHealInfo(&healInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellTypeMaskNoMatch) +{ + // Proc requires heal but event is damage + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) // Mismatch + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// SpellPhaseMask Tests - Cast vs Hit vs Finish +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellPhaseMaskCast) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellPhaseMaskHit) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellPhaseMaskNoMatch) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + // Proc requires cast phase + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .Build(); + + // Event is hit phase + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_CastPhaseWithExplicitHitMaskCrit) +{ + // Nature's Grace scenario: CAST phase + explicit HitMask for crit + // Crit is pre-calculated for travel-time spells + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_NATURE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(PROC_HIT_CRITICAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_CastPhaseWithExplicitHitMaskNoCrit) +{ + // CAST phase + explicit HitMask requires crit, but spell didn't crit + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_NATURE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(PROC_HIT_NORMAL) // No crit + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_CastPhaseWithDefaultHitMask) +{ + // CAST phase + HitMask=0 should skip HitMask check (old behavior) + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_NATURE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(0) // Default - no explicit HitMask + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_CAST) + .WithHitMask(PROC_HIT_NORMAL) // Doesn't matter - HitMask check skipped + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// Combined Condition Tests +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_AllConditionsMatch) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_OneConditionFails) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) // Requires crit + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) // But we got normal hit + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_ZeroProcFlags) +{ + // Zero proc flags should never match anything + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(0) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_PeriodicDamage) +{ + auto* spellInfo = CreateSpellInfo(1); + DamageInfo damageInfo(nullptr, nullptr, 50, spellInfo, SPELL_SCHOOL_MASK_SHADOW, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_PERIODIC) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_PERIODIC) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_TakenDamage) +{ + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_DAMAGE) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_DAMAGE) + .WithHitMask(PROC_HIT_NORMAL) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// SpellFamilyName/SpellFamilyFlags Tests - Class-specific proc matching +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyNameMatch) +{ + // Create a Mage spell (SpellFamilyName = SPELLFAMILY_MAGE = 3) + auto* spellInfo = SpellInfoBuilder() + .WithId(133) // Fireball + .WithSpellFamilyName(SPELLFAMILY_MAGE) + .Build(); + _spellInfos.push_back(spellInfo); + + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + // Proc entry requires Mage spells + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellFamilyName(SPELLFAMILY_MAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyNameNoMatch) +{ + // Create a Warlock spell but proc requires Mage + auto* spellInfo = SpellInfoBuilder() + .WithId(686) // Shadow Bolt + .WithSpellFamilyName(SPELLFAMILY_WARLOCK) + .Build(); + _spellInfos.push_back(spellInfo); + + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_SHADOW, SPELL_DIRECT_DAMAGE); + + // Proc entry requires Mage spells + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellFamilyName(SPELLFAMILY_MAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyFlagsMatch) +{ + // Create a Paladin Holy Light spell with specific family flags + auto* spellInfo = SpellInfoBuilder() + .WithId(635) // Holy Light + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellFamilyFlags(0x80000000, 0, 0) // Example flag for Holy Light + .Build(); + _spellInfos.push_back(spellInfo); + + HealInfo healInfo(nullptr, nullptr, 500, spellInfo, SPELL_SCHOOL_MASK_HOLY); + + // Proc entry requires specific Paladin family flag + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellFamilyMask(flag96(0x80000000, 0, 0)) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithHealInfo(&healInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyFlagsNoMatch) +{ + // Create a Paladin spell with different family flags + auto* spellInfo = SpellInfoBuilder() + .WithId(19750) // Flash of Light + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellFamilyFlags(0x40000000, 0, 0) // Different flag + .Build(); + _spellInfos.push_back(spellInfo); + + HealInfo healInfo(nullptr, nullptr, 300, spellInfo, SPELL_SCHOOL_MASK_HOLY); + + // Proc entry requires different family flag + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellFamilyMask(flag96(0x80000000, 0, 0)) // Wants Holy Light flag + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithHealInfo(&healInfo) + .Build(); + + EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyNameZeroAcceptsAll) +{ + // When SpellFamilyName is 0, it should accept any spell family + auto* spellInfo = SpellInfoBuilder() + .WithId(100) + .WithSpellFamilyName(SPELLFAMILY_DRUID) + .Build(); + _spellInfos.push_back(spellInfo); + + DamageInfo damageInfo(nullptr, nullptr, 100, spellInfo, SPELL_SCHOOL_MASK_NATURE, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellFamilyName(0) // Accept any family + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SpellFamilyFlagsZeroAcceptsAll) +{ + // When SpellFamilyMask is 0, it should accept any flags within the family + auto* spellInfo = SpellInfoBuilder() + .WithId(100) + .WithSpellFamilyName(SPELLFAMILY_PRIEST) + .WithSpellFamilyFlags(0x12345678, 0xABCDEF01, 0x87654321) // Any flags + .Build(); + _spellInfos.push_back(spellInfo); + + HealInfo healInfo(nullptr, nullptr, 200, spellInfo, SPELL_SCHOOL_MASK_HOLY); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellFamilyName(SPELLFAMILY_PRIEST) + .WithSpellFamilyMask(flag96(0, 0, 0)) // Accept any flags + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithHealInfo(&healInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +// ============================================================================= +// Real-world Spell Proc Examples +// ============================================================================= + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_HotStreakScenario) +{ + // Hot Streak: Proc on critical damage spell from Mage + auto* fireballSpell = SpellInfoBuilder() + .WithId(133) + .WithSpellFamilyName(SPELLFAMILY_MAGE) + .WithSpellFamilyFlags(0x00000001, 0, 0) // Fireball flag + .Build(); + _spellInfos.push_back(fireballSpell); + + DamageInfo damageInfo(nullptr, nullptr, 1000, fireballSpell, SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE); + + // Hot Streak proc entry - triggers on fire spell crits + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellFamilyName(SPELLFAMILY_MAGE) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_IlluminationScenario) +{ + // Illumination: Proc on critical heals from Paladin + auto* holyLightSpell = SpellInfoBuilder() + .WithId(635) + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellFamilyFlags(0x80000000, 0, 0) + .Build(); + _spellInfos.push_back(holyLightSpell); + + HealInfo healInfo(nullptr, nullptr, 2000, holyLightSpell, SPELL_SCHOOL_MASK_HOLY); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellFamilyName(SPELLFAMILY_PALADIN) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) + .WithSpellTypeMask(PROC_SPELL_TYPE_HEAL) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_CRITICAL) + .WithHealInfo(&healInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SecondWindScenario) +{ + // Second Wind: Proc when stunned/immobilized (taken hit with dodge/parry) + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK | PROC_FLAG_TAKEN_SPELL_MELEE_DMG_CLASS) + .WithHitMask(PROC_HIT_DODGE | PROC_HIT_PARRY) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK) + .WithHitMask(PROC_HIT_DODGE) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} + +TEST_F(SpellProcTest, CanSpellTriggerProcOnEvent_SwordAndBoardScenario) +{ + // Sword and Board: Proc on Devastate/Revenge (block effects) + auto* devastateSpell = SpellInfoBuilder() + .WithId(20243) // Devastate + .WithSpellFamilyName(SPELLFAMILY_WARRIOR) + .WithSpellFamilyFlags(0x00000000, 0x00000000, 0x00000100) // Devastate flag + .Build(); + _spellInfos.push_back(devastateSpell); + + DamageInfo damageInfo(nullptr, nullptr, 500, devastateSpell, SPELL_SCHOOL_MASK_NORMAL, SPELL_DIRECT_DAMAGE); + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS) + .WithSpellFamilyName(SPELLFAMILY_WARRIOR) + .WithSpellFamilyMask(flag96(0, 0, 0x100)) // Devastate flag + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .Build(); + + auto eventInfo = ProcEventInfoBuilder() + .WithTypeMask(PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS) + .WithSpellPhaseMask(PROC_SPELL_PHASE_HIT) + .WithHitMask(PROC_HIT_NORMAL) + .WithDamageInfo(&damageInfo) + .Build(); + + EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo)); +} diff --git a/src/test/server/game/Spells/SpellProcTestData.h b/src/test/server/game/Spells/SpellProcTestData.h new file mode 100644 index 000000000..c0915b2ae --- /dev/null +++ b/src/test/server/game/Spells/SpellProcTestData.h @@ -0,0 +1,1020 @@ +/* + * This file is part of the AzerothCore Project. + * AUTO-GENERATED - DO NOT EDIT + * Generated by generate_spell_proc_data.py + */ + +#ifndef AZEROTHCORE_SPELL_PROC_TEST_DATA_H +#define AZEROTHCORE_SPELL_PROC_TEST_DATA_H + +#include "SpellMgr.h" +#include +#include + +/** + * @brief Test data entry from spell_proc table with DBC comparison data + */ +struct SpellProcTestEntry +{ + // spell_proc table fields + int32_t SpellId; + uint32_t SchoolMask; + uint32_t SpellFamilyName; + uint32_t SpellFamilyMask0; + uint32_t SpellFamilyMask1; + uint32_t SpellFamilyMask2; + uint32_t ProcFlags; + uint32_t SpellTypeMask; + uint32_t SpellPhaseMask; + uint32_t HitMask; + uint32_t AttributesMask; + uint32_t Cooldown; + float Chance; + float ProcsPerMinute; + uint8_t Charges; + uint8_t DisableEffectsMask; + + // DBC fields for comparison (from Spell.dbc) + uint32_t DBC_ProcFlags; + uint32_t DBC_ProcChance; + uint32_t DBC_ProcCharges; + + /** + * @brief Convert to SpellProcEntry for testing + */ + SpellProcEntry ToSpellProcEntry() const + { + SpellProcEntry entry = {}; + entry.SchoolMask = SchoolMask; + entry.SpellFamilyName = SpellFamilyName; + entry.SpellFamilyMask[0] = SpellFamilyMask0; + entry.SpellFamilyMask[1] = SpellFamilyMask1; + entry.SpellFamilyMask[2] = SpellFamilyMask2; + entry.ProcFlags = ProcFlags; + entry.SpellTypeMask = SpellTypeMask; + entry.SpellPhaseMask = SpellPhaseMask; + entry.HitMask = HitMask; + entry.AttributesMask = AttributesMask; + entry.Cooldown = Milliseconds(Cooldown); + entry.Chance = Chance; + entry.ProcsPerMinute = ProcsPerMinute; + entry.Charges = Charges; + entry.DisableEffectsMask = DisableEffectsMask; + return entry; + } + + /** + * @brief Check if this entry adds value beyond DBC defaults + * + * Returns true if the spell_proc entry provides functionality + * not available in Spell.dbc alone. + */ + bool AddsValueBeyondDBC() const + { + // New fields not in DBC (always add value) + if (ProcsPerMinute > 0) return true; + if (Cooldown > 0) return true; + if (SpellTypeMask != 0) return true; + if (SpellPhaseMask != 0) return true; + if (HitMask != 0) return true; + if (DisableEffectsMask != 0) return true; + if (AttributesMask != 0) return true; + if (SchoolMask != 0) return true; + if (SpellFamilyMask0 != 0 || SpellFamilyMask1 != 0 || SpellFamilyMask2 != 0) return true; + + // Override fields (add value if different from DBC) + if (ProcFlags != 0 && ProcFlags != DBC_ProcFlags) return true; + if (Chance != 0 && static_cast(Chance) != DBC_ProcChance) return true; + if (Charges != 0 && Charges != DBC_ProcCharges) return true; + + return false; + } + + /** + * @brief Check if DBC values are available for this entry + */ + bool HasDBCData() const + { + return DBC_ProcFlags != 0 || DBC_ProcChance != 0 || DBC_ProcCharges != 0; + } +}; + +/** + * @brief All spell_proc entries from the database + * Total: 869 entries + */ +inline std::vector GetAllSpellProcTestEntries() +{ + return { + { -66799, 0, 15, 4194304, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -65661, 0, 15, 4194321, 537001988, 0, 16, 1, 2, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -64127, 0, 6, 1, 1, 0, 0, 6, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -63730, 0, 6, 2048, 4, 0, 0, 0, 2, 0, 0, 100, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -63373, 0, 11, 2147483648, 0, 0, 65536, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -63156, 0, 5, 1, 192, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -62764, 0, 9, 0, 268435456, 0, 65536, 4, 2, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -61846, 0, 0, 0, 0, 0, 64, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -61680, 0, 9, 0, 268435456, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -59887, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -59088, 0, 4, 0, 2, 0, 1024, 4, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -58872, 0, 0, 0, 0, 0, 0, 1, 0, 8259, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -57878, 0, 0, 0, 0, 0, 0, 1, 0, 16, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -57470, 0, 6, 1, 0, 0, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -56636, 0, 4, 32, 0, 0, 0, 0, 2, 0, 0, 5800, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -56342, 0, 9, 24, 134217728, 147456, 0, 0, 4, 0, 2, 22000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -55666, 0, 15, 1, 134217728, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -54747, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -54639, 0, 15, 4194304, 65536, 0, 0, 0, 2, 0, 0, 100, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53709, 2, 10, 16384, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53695, 0, 10, 8388608, 0, 8, 16, 5, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53671, 0, 10, 8388608, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53583, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53569, 0, 10, 1075838976, 65536, 0, 0, 3, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53551, 0, 10, 4096, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53527, 1, 10, 0, 0, 4, 1024, 0, 2, 1, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53501, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53486, 0, 10, 8388608, 163840, 0, 0, 0, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53380, 0, 10, 8388608, 163840, 0, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53290, 0, 9, 2048, 1, 512, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53256, 0, 9, 2048, 8388609, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53234, 0, 9, 131072, 1, 1, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53228, 0, 9, 32, 16777216, 0, 0, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53221, 0, 9, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53215, 0, 9, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -53178, 0, 9, 0, 268435456, 0, 65536, 4, 2, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -52795, 0, 6, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -52127, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51940, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51692, 0, 8, 516, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51682, 0, 8, 0, 524288, 0, 0, 4, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51672, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51664, 0, 8, 131072, 8, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51634, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51627, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51625, 0, 8, 268476416, 0, 0, 0, 5, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51562, 0, 11, 256, 0, 16, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51556, 0, 11, 192, 0, 16, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51525, 0, 11, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51523, 0, 11, 0, 1, 0, 65536, 0, 2, 0, 0, 0, 50.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51521, 0, 11, 0, 16777216, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51474, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -51459, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -50880, 0, 15, 0, 67108864, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49467, 0, 15, 16, 131072, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49223, 0, 15, 17, 134348800, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49219, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49217, 0, 15, 0, 0, 2, 0, 0, 2, 0, 0, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49208, 0, 15, 4194304, 65536, 0, 0, 0, 2, 0, 0, 100, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49200, 126, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49188, 0, 15, 0, 131072, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49182, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49149, 0, 15, 6, 131074, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49027, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 20000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49018, 0, 15, 20971520, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49015, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -49004, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48988, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48979, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48539, 0, 7, 16, 67108864, 0, 262144, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48516, 0, 7, 5, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48506, 0, 7, 5, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48496, 0, 7, 96, 33554434, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -48483, 0, 7, 34816, 1088, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47580, 0, 6, 0, 0, 64, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47569, 0, 6, 16384, 0, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47516, 0, 6, 6144, 65536, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47509, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47263, 32, 5, 0, 0, 0, 0, 0, 2, 2, 0, 20000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47258, 0, 5, 0, 8388608, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47245, 0, 5, 2, 0, 0, 262144, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47230, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47201, 0, 5, 16393, 262144, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -47195, 0, 5, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -46951, 0, 4, 1024, 64, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -46945, 0, 4, 0, 65536, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -46913, 0, 4, 64, 1028, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -46867, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -46854, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -45234, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44557, 0, 3, 32, 0, 0, 0, 0, 2, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44546, 0, 3, 736, 4096, 0, 69632, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44449, 0, 3, 551686775, 102472, 0, 0, 0, 2, 2, 0, 8, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44445, 0, 3, 19, 69632, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44442, 0, 3, 8388608, 64, 0, 0, 0, 2, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -44404, 0, 3, 536870945, 36864, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -41635, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -35541, 0, 0, 0, 0, 0, 8388608, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -35100, 0, 9, 4096, 0, 1, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34950, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34935, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34914, 0, 6, 8192, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34753, 0, 6, 6144, 4, 4096, 0, 0, 2, 2, 2, 1, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34500, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -34497, 0, 9, 395264, 8388609, 513, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -33881, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -33191, 0, 6, 32768, 1024, 64, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -33150, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -33142, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -33076, 0, 0, 0, 0, 0, 664232, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -32385, 0, 5, 1, 262144, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31876, 0, 10, 8388608, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31871, 0, 10, 16, 0, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31833, 0, 10, 2147483648, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31785, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31656, 4, 3, 134217728, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31641, 0, 0, 0, 0, 0, 680, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31571, 0, 3, 0, 34, 0, 16384, 7, 4, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31569, 0, 3, 65536, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31244, 0, 8, 3801088, 9, 0, 0, 5, 2, 11196, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31226, 0, 8, 0, 524288, 0, 0, 5, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -31124, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30881, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30701, 28, 0, 0, 0, 0, 664232, 1, 0, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30675, 0, 11, 3, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30482, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30299, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30293, 0, 5, 385, 8519872, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -30160, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -29834, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -29723, 0, 4, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -29593, 0, 0, 0, 0, 0, 0, 1, 0, 112, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -29441, 0, 0, 0, 0, 0, 0, 7, 0, 8, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -29074, 20, 3, 0, 0, 0, 0, 0, 2, 2, 0, 8, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -27811, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -27243, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20925, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20500, 0, 4, 268435456, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20335, 0, 10, 8388608, 0, 0, 16, 5, 2, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20234, 0, 10, 32768, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20210, 0, 10, 3221225472, 65536, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20177, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -20049, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -19572, 0, 9, 8388608, 0, 0, 262144, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -19184, 0, 9, 16, 8192, 0, 0, 0, 4, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -18213, 32, 5, 16384, 0, 0, 2, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -18119, 0, 5, 0, 8388608, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -18096, 0, 5, 256, 8388608, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -18094, 0, 5, 10, 0, 0, 0, 1, 2, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -17793, 0, 5, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -17106, 0, 7, 524288, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16958, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16952, 0, 7, 233472, 1024, 262144, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16880, 72, 7, 103, 58720258, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16689, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16487, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16257, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16256, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16180, 0, 11, 448, 0, 16, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -16176, 0, 11, 448, 0, 16, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -15337, 0, 6, 8396800, 2, 0, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -14892, 0, 6, 268443136, 65540, 0, 0, 0, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -14531, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -14186, 0, 8, 1107296782, 2, 0, 0, 0, 2, 2, 2, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -14156, 0, 8, 4063232, 9, 0, 0, 0, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -14143, 0, 8, 1191182854, 2097152, 0, 0, 1, 2, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -13983, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -13754, 0, 8, 16, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -13165, 0, 0, 0, 0, 0, 64, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12966, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12834, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12319, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12317, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12311, 0, 4, 2048, 1, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12298, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12289, 0, 4, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -12281, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11255, 0, 3, 16384, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11213, 0, 3, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11185, 0, 3, 128, 0, 0, 65536, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11180, 16, 3, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11119, 4, 3, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11103, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -11095, 0, 3, 16, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -10400, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 8, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -9799, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -9452, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -7302, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -7001, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -5952, 0, 8, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -1463, 0, 3, 0, 0, 0, 0, 1, 0, 1024, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -1120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -974, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -588, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -324, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { -168, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 1719, 0, 4, 778044484, 4212549, 0, 0, 1, 2, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 4341, 0, 0, 0, 0, 0, 4096, 1, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 4524, 0, 0, 0, 0, 0, 1048576, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 5118, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 6346, 0, 0, 0, 0, 0, 0, 0, 0, 256, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 7383, 1, 0, 0, 0, 0, 0, 1, 0, 256, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 7434, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 8178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 9782, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 9784, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 11129, 4, 3, 146800663, 200776, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12043, 0, 3, 1631584309, 4096, 0, 0, 7, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12169, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12322, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12328, 0, 4, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12536, 0, 3, 549591799, 168000, 0, 0, 0, 1, 0, 12, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 12999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13001, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13159, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13163, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 20000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 13234, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15088, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 5000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15128, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15277, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15286, 32, 6, 41984016, 9218, 8, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15346, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 15600, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2000, 2.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16164, 28, 0, 0, 0, 0, 65536, 1, 2, 2, 0, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16166, 0, 11, 3, 4096, 0, 0, 7, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16246, 0, 11, 2551185859, 5120, 16, 0, 0, 1, 0, 12, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16372, 0, 0, 0, 0, 0, 131072, 0, 0, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16550, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16620, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16624, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16864, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 16870, 0, 7, 14924799, 126879699, 263168, 0, 0, 1, 0, 12, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17116, 0, 7, 268436065, 33554464, 32768, 0, 7, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17364, 8, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17495, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17619, 0, 13, 0, 0, 0, 34816, 7, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17670, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 17941, 0, 5, 1, 0, 0, 65536, 1, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 18708, 0, 5, 536870912, 0, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 18820, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20128, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20131, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20132, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20164, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20165, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20166, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20185, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20186, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20375, 1, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20705, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20784, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 20911, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 21084, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 21185, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 21882, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 21890, 0, 4, 712396527, 876, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 22007, 0, 3, 2097185, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 22008, 0, 3, 1631584309, 0, 0, 69632, 5, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 22618, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 22648, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 120000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23547, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23548, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23551, 0, 11, 192, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23552, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23572, 0, 11, 192, 0, 0, 0, 0, 2, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23578, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23581, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23591, 0, 10, 8388608, 0, 0, 16, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23686, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23688, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23689, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23721, 0, 9, 2048, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 23920, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 24353, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 24389, 4, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 24658, 0, 0, 0, 0, 0, 87376, 7, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 24905, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 24932, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 25050, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 25669, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 25899, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26107, 0, 7, 8388608, 268435584, 0, 0, 0, 2, 116, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26119, 0, 0, 2416967683, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26128, 0, 0, 0, 0, 0, 0, 0, 2, 8, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26135, 0, 10, 8388608, 0, 0, 16, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26169, 0, 6, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26467, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26480, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 26605, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27419, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27498, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27521, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27539, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27656, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27774, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 27787, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28200, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28305, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28716, 0, 7, 16, 0, 0, 262144, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28719, 0, 7, 32, 0, 0, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28744, 0, 7, 64, 0, 0, 278528, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28752, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28789, 0, 10, 3221225472, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28802, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28809, 0, 6, 4096, 0, 0, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28812, 0, 8, 33554438, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28816, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28823, 0, 11, 192, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28845, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28847, 0, 7, 32, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 28849, 0, 11, 128, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29150, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29307, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29385, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29455, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29501, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29601, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29624, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29625, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29626, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29632, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29633, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29634, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29635, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29636, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29637, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 29977, 4, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 30003, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 30823, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 30937, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 31394, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 31794, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 31801, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 31834, 0, 10, 2147483648, 0, 0, 16384, 2, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 31904, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32065, 0, 0, 0, 0, 0, 524288, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32216, 0, 4, 0, 256, 0, 16, 1, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32409, 0, 0, 0, 8192, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32587, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32642, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32734, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32748, 0, 8, 0, 1, 0, 320, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32776, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32777, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32837, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 35000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32844, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32863, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 32885, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33089, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33297, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33299, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33510, 0, 0, 0, 0, 0, 340, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33648, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33719, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33746, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33757, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 3000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33759, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 33953, 0, 0, 0, 0, 0, 279552, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34074, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34080, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34138, 0, 11, 128, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34139, 0, 10, 1073741824, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34258, 0, 10, 8388608, 0, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34262, 0, 10, 8388608, 0, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34320, 0, 0, 0, 0, 0, 0, 3, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34355, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34477, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34584, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34586, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34598, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34749, 0, 0, 0, 0, 0, 0, 0, 2, 8, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34774, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 20000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34783, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34827, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 34936, 0, 5, 1, 64, 0, 65536, 1, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35077, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35080, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35083, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35086, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35121, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35321, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 35399, 0, 0, 0, 0, 0, 131072, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36032, 0, 3, 4096, 32768, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36096, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36111, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36123, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36541, 4, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 36659, 0, 0, 0, 0, 0, 524288, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37165, 0, 8, 2098176, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37168, 0, 8, 4063232, 9, 0, 0, 0, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37170, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37173, 0, 8, 750519704, 262, 0, 0, 0, 2, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37189, 0, 10, 3221225472, 0, 0, 0, 0, 2, 2, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37193, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37195, 0, 10, 8388608, 0, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37197, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37213, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37214, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37227, 0, 11, 448, 0, 0, 0, 0, 2, 2, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37237, 0, 11, 1, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37247, 8, 0, 0, 0, 0, 0, 0, 2, 0, 0, 40000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37288, 0, 7, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37295, 0, 7, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37377, 32, 5, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37379, 32, 5, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37381, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37384, 0, 5, 1, 64, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37443, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37447, 0, 3, 0, 256, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37514, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37516, 0, 4, 1024, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37519, 0, 0, 0, 0, 0, 0, 0, 2, 48, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37523, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37528, 0, 4, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37536, 0, 4, 65536, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37568, 0, 6, 2048, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37594, 0, 6, 4096, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37600, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37601, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37603, 0, 6, 32768, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37604, 0, 6, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37655, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 37657, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 2500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38026, 1, 0, 0, 0, 0, 0, 1, 0, 256, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38031, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38164, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 120000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38252, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38290, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38299, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38326, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38327, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38334, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38347, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38350, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38363, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38394, 0, 5, 6, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 38857, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39027, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39215, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39367, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39372, 48, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39437, 4, 5, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39442, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39443, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39530, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 39958, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 40000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40407, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40438, 0, 6, 32832, 0, 0, 0, 3, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40442, 0, 7, 20, 1088, 0, 0, 7, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40444, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40458, 0, 4, 33554432, 1537, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40463, 0, 11, 129, 16, 0, 0, 3, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40470, 0, 10, 3229614080, 0, 0, 0, 3, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40475, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40478, 0, 5, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40482, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40485, 0, 9, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40816, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 7000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40899, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 40971, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41034, 126, 0, 0, 0, 0, 0, 0, 0, 1024, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41260, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41262, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41350, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41381, 0, 0, 0, 0, 0, 0, 1, 0, 256, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41393, 0, 0, 0, 0, 0, 0, 1, 0, 32, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41434, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41469, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 41989, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42083, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42135, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 90000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42136, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 90000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42368, 0, 10, 1073741824, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42370, 0, 11, 128, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 42770, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43443, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43726, 0, 10, 1073741824, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43728, 0, 11, 128, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43737, 0, 7, 0, 1088, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43739, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43741, 0, 10, 2147483648, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43745, 0, 10, 0, 512, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43748, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43750, 0, 11, 1, 0, 0, 0, 0, 2, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 43819, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 44141, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 44401, 0, 3, 0, 0, 0, 69632, 5, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 44543, 0, 3, 1049120, 4096, 0, 0, 0, 1, 0, 2, 0, 7.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 44545, 0, 3, 1049120, 4096, 0, 0, 0, 1, 0, 2, 0, 15.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45054, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45057, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45092, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45354, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45355, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45469, 0, 15, 16, 0, 0, 16, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45481, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45482, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45483, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 45484, 0, 0, 0, 0, 0, 16384, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46025, 32, 6, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46092, 0, 10, 1073741824, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46098, 0, 11, 128, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46569, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46662, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 25000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46832, 0, 7, 1, 0, 0, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46910, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46911, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 46916, 0, 4, 2097152, 0, 0, 0, 0, 4, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 47383, 0, 5, 0, 192, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 47981, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 48108, 0, 3, 4194304, 0, 0, 65536, 1, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 48504, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 48833, 0, 7, 0, 1088, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 48835, 0, 10, 8388608, 0, 0, 0, 0, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 48837, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49005, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49028, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49194, 0, 15, 0, 0, 1, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49222, 0, 0, 0, 0, 0, 139944, 1, 0, 0, 0, 2000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49592, 0, 0, 0, 0, 0, 8528552, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49622, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 49796, 0, 15, 2, 131078, 0, 0, 0, 4, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 50240, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 50421, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 50781, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 50871, 0, 9, 0, 1073741824, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 50908, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51124, 0, 15, 2, 6, 0, 65552, 1, 4, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51346, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51349, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51352, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51359, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51414, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51528, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51529, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51530, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51531, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51532, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51698, 0, 0, 0, 0, 0, 0, 3, 4, 2, 0, 1000, 33.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51700, 0, 0, 0, 0, 0, 0, 3, 4, 2, 0, 1000, 66.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51701, 0, 0, 0, 0, 0, 0, 3, 4, 2, 0, 1000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 51915, 0, 0, 0, 0, 0, 16777216, 0, 0, 0, 0, 600000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52020, 0, 7, 32768, 1048576, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52420, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52423, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52437, 1, 4, 536870912, 0, 0, 16, 0, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52881, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 52898, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53257, 0, 9, 0, 268435456, 0, 16, 1, 2, 2, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53386, 0, 15, 2182250279, 447, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53397, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53515, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53601, 0, 0, 0, 0, 0, 1048576, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53646, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 5000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53651, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53736, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 53817, 0, 11, 451, 32768, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54274, 0, 5, 357, 131264, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54276, 0, 5, 357, 131264, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54277, 0, 5, 357, 131264, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54278, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54646, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54695, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54707, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54738, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54741, 0, 3, 4, 0, 0, 65536, 5, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54748, 0, 0, 0, 0, 0, 0, 0, 0, 259, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54754, 0, 7, 16, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54808, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54815, 0, 7, 32768, 0, 0, 16, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54821, 0, 7, 4096, 0, 0, 16, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54832, 0, 7, 0, 4096, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54838, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54841, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 2500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54845, 0, 7, 4, 0, 0, 65536, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54909, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54925, 2, 10, 0, 512, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54937, 0, 10, 2147483648, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 54939, 0, 10, 32768, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55198, 0, 11, 448, 0, 0, 16384, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55380, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 40000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55381, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55440, 0, 11, 64, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55640, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55677, 0, 6, 0, 1, 0, 0, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55680, 0, 6, 512, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55681, 0, 6, 32768, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55689, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55747, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55768, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 55776, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 55000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56218, 0, 5, 2, 0, 0, 0, 1, 2, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56249, 0, 5, 0, 0, 1024, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56355, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56364, 0, 3, 0, 16777216, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56372, 0, 3, 0, 128, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56374, 0, 3, 0, 16384, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56375, 0, 3, 16777216, 0, 0, 65536, 4, 2, 2049, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56451, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56800, 0, 8, 4, 0, 0, 16, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56816, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56817, 0, 15, 0, 536870912, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56821, 0, 8, 2, 0, 0, 0, 0, 2, 2, 0, 500, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 56841, 0, 9, 2048, 0, 0, 256, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57345, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57351, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57352, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57529, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57531, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57761, 0, 3, 1, 4096, 0, 65536, 1, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57870, 0, 9, 8388608, 0, 0, 262144, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57907, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 57989, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58357, 0, 4, 64, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58364, 0, 4, 1024, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58372, 0, 4, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58375, 0, 4, 0, 512, 0, 16, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58386, 0, 0, 0, 0, 0, 0, 0, 2, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58442, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58444, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 5000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58616, 0, 15, 16777216, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58620, 0, 15, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58626, 0, 15, 33554432, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58642, 0, 15, 0, 134217728, 0, 16, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58677, 0, 15, 8192, 0, 0, 0, 2, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58877, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 58901, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59176, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59327, 0, 15, 134217728, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59345, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59630, 0, 0, 0, 0, 0, 69648, 5, 1, 0, 2, 35000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59725, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59906, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 59915, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60061, 0, 0, 0, 0, 0, 294912, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60063, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60066, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60132, 0, 15, 16, 134348800, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60170, 0, 5, 6, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60172, 0, 5, 262144, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60176, 0, 4, 32, 16, 0, 262144, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60221, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60301, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60306, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60317, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60436, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60442, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60473, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60482, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60487, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60490, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60493, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60503, 1, 4, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60519, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60524, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60529, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60537, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60564, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60571, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60572, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60573, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60574, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60575, 0, 11, 2416967680, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60617, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60710, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60717, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60719, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60722, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60724, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60726, 0, 7, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60770, 0, 11, 1, 0, 0, 0, 0, 2, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60818, 0, 10, 0, 512, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 60826, 0, 15, 20971520, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61062, 0, 3, 0, 256, 0, 16384, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61188, 0, 5, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61257, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61324, 0, 10, 0, 131072, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61356, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61618, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 61848, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62114, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62115, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62147, 0, 15, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62259, 0, 15, 33554432, 0, 0, 0, 0, 0, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62337, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62459, 0, 15, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62600, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 62606, 0, 0, 0, 0, 0, 0, 0, 0, 1027, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63057, 0, 7, 0, 262144, 0, 16384, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63086, 0, 9, 0, 0, 65536, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63108, 0, 5, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63251, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63279, 0, 11, 0, 1024, 0, 0, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63280, 0, 11, 536870912, 0, 0, 0, 0, 1, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63310, 0, 5, 0, 65536, 0, 65536, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63320, 0, 5, 2147745792, 0, 32768, 1024, 7, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63335, 0, 15, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63611, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 63849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64343, 0, 3, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64411, 0, 0, 0, 0, 0, 279552, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64415, 0, 0, 0, 0, 0, 279552, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64440, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64571, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64714, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64738, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64742, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64752, 0, 7, 4096, 256, 2097152, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64764, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64786, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64792, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64823, 0, 7, 4, 0, 0, 65536, 1, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64824, 0, 7, 2097152, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64860, 0, 9, 0, 1, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64867, 0, 3, 536870945, 4096, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64882, 0, 10, 0, 1048576, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64890, 0, 10, 0, 65536, 0, 0, 2, 2, 2, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64908, 0, 6, 8192, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64912, 0, 6, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64914, 0, 8, 65536, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64928, 0, 11, 1, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64938, 0, 4, 2097216, 0, 0, 0, 0, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64952, 0, 7, 0, 1088, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64955, 0, 10, 0, 64, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64964, 0, 15, 0, 536870912, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64976, 0, 4, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 64999, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65002, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65005, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65007, 0, 0, 0, 0, 0, 81920, 3, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65013, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65020, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65025, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 65032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 66808, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 66865, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3000, 35.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 66889, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67115, 0, 15, 20971520, 0, 0, 0, 0, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67151, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67209, 1, 8, 1048576, 0, 0, 0, 0, 2, 0, 0, 15000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67228, 0, 11, 0, 4096, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67353, 0, 7, 32768, 1049856, 0, 0, 0, 2, 0, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67356, 8, 7, 16, 0, 0, 0, 2, 2, 0, 0, 5000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67361, 0, 7, 2, 0, 0, 0, 1, 2, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67363, 0, 10, 2147483648, 0, 0, 0, 0, 2, 0, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67365, 0, 10, 0, 2048, 0, 0, 0, 2, 0, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67379, 0, 10, 0, 262144, 0, 0, 0, 2, 0, 0, 9000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67381, 0, 15, 0, 536870912, 0, 0, 0, 2, 0, 0, 10000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67384, 0, 15, 16, 134348800, 0, 0, 0, 2, 0, 0, 10000, 80.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67386, 0, 11, 1, 0, 0, 65536, 0, 1, 0, 0, 6000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67389, 0, 11, 256, 0, 0, 16384, 0, 1, 0, 0, 8000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67392, 0, 11, 0, 0, 4, 16, 0, 2, 0, 0, 9000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67530, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 5000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67653, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67667, 0, 0, 0, 0, 0, 16384, 2, 1, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67670, 0, 0, 0, 0, 0, 65536, 1, 1, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67672, 0, 0, 0, 0, 0, 8388948, 1, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67698, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67702, 1, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67712, 0, 0, 0, 0, 0, 69632, 1, 2, 2, 0, 2000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67752, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67758, 0, 0, 0, 0, 0, 69632, 1, 2, 2, 0, 2000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 67771, 1, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 68051, 1, 4, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 68160, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 69739, 0, 0, 0, 0, 0, 0, 7, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 69755, 0, 0, 0, 0, 0, 0, 7, 2, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 69762, 0, 0, 0, 0, 0, 87040, 0, 1, 0, 256, 100, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70188, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 3000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70388, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70652, 0, 15, 8, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70656, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70664, 0, 7, 16, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70672, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70723, 0, 7, 5, 0, 0, 0, 1, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70727, 0, 9, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70730, 0, 9, 16384, 4096, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70748, 0, 3, 0, 2097152, 0, 1024, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70756, 0, 10, 0, 65536, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70761, 0, 10, 0, 0, 1, 1024, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70770, 0, 6, 2048, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70803, 0, 8, 4063232, 8, 0, 0, 0, 4, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70805, 0, 8, 0, 131072, 0, 0, 4, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70807, 0, 11, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70808, 0, 11, 256, 0, 0, 0, 2, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70811, 0, 11, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70817, 0, 11, 0, 4096, 0, 65536, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70830, 0, 11, 0, 131072, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70841, 0, 5, 4, 256, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70844, 0, 4, 256, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70854, 0, 4, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 70904, 0, 6, 0, 0, 2048, 2048, 4, 0, 0, 0, 1000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71162, 0, 5, 0, 192, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71165, 0, 5, 0, 192, 0, 0, 0, 1, 0, 8, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71174, 1, 7, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71176, 0, 7, 2097154, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71178, 0, 7, 16, 0, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71186, 0, 10, 0, 32768, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71191, 0, 10, 0, 65536, 0, 0, 2, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71194, 0, 10, 0, 1048576, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71198, 4, 11, 268435456, 0, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71214, 0, 11, 0, 16, 0, 16, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71217, 0, 11, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71226, 0, 15, 16, 134348800, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71228, 0, 15, 0, 536870912, 0, 0, 0, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71402, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71404, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71406, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 50.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71519, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 105000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71540, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71545, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 50.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71562, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 105000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71564, 126, 0, 0, 0, 0, 0, 3, 2, 2, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71567, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 250, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71571, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71573, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71585, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71602, 0, 0, 0, 0, 0, 65536, 1, 1, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71604, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71606, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 100000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71611, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71634, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71637, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 100000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71640, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 30000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71642, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71645, 0, 0, 0, 0, 0, 65536, 1, 1, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71756, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71761, 0, 3, 0, 1048576, 0, 0, 5, 2, 256, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71770, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71865, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71868, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71880, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71892, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71903, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 71993, 0, 0, 0, 0, 0, 4, 0, 0, 12287, 0, 3000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72059, 0, 0, 0, 0, 0, 0, 1, 0, 1027, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72176, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72256, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72413, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 60000, 10.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72417, 0, 0, 0, 0, 0, 327680, 1, 2, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72419, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 60000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72455, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72673, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72674, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72675, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10000, 100.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72782, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72783, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72784, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72832, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 72833, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 74396, 84, 3, 685904631, 1151048, 0, 65536, 0, 1, 0, 0, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75455, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75457, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75465, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75474, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 50000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75475, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75481, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 45000, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75490, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 0, 0.0f, 0.0f, 0, 0, 0, 0, 0 }, + { 75495, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 0, 0.0f } + }; +} + +/** + * @brief Group entries by ProcFlags pattern for targeted testing + */ +inline std::map> GroupByProcFlags() +{ + std::map> grouped; + for (auto const& entry : GetAllSpellProcTestEntries()) + { + grouped[entry.ProcFlags].push_back(entry); + } + return grouped; +} + +/** + * @brief Group entries by HitMask for targeted testing + */ +inline std::map> GroupByHitMask() +{ + std::map> grouped; + for (auto const& entry : GetAllSpellProcTestEntries()) + { + grouped[entry.HitMask].push_back(entry); + } + return grouped; +} + +/** + * @brief Group entries by SpellFamilyName for class-specific testing + */ +inline std::map> GroupBySpellFamily() +{ + std::map> grouped; + for (auto const& entry : GetAllSpellProcTestEntries()) + { + grouped[entry.SpellFamilyName].push_back(entry); + } + return grouped; +} + +#endif //AZEROTHCORE_SPELL_PROC_TEST_DATA_H diff --git a/src/test/server/game/Spells/SpellProcTriggeredFilterTest.cpp b/src/test/server/game/Spells/SpellProcTriggeredFilterTest.cpp new file mode 100644 index 000000000..997425915 --- /dev/null +++ b/src/test/server/game/Spells/SpellProcTriggeredFilterTest.cpp @@ -0,0 +1,391 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellProcTriggeredFilterTest.cpp + * @brief Unit tests for triggered spell filtering in proc system + * + * Tests the logic from SpellAuras.cpp:2191-2209: + * - Self-loop prevention (spell triggered by same aura) + * - Triggered spell blocking (default behavior) + * - SPELL_ATTR3_CAN_PROC_FROM_PROCS exception + * - PROC_ATTR_TRIGGERED_CAN_PROC exception + * - SPELL_ATTR3_NOT_A_PROC exception + * - AUTO_ATTACK_PROC_FLAG_MASK exception + */ + +#include "ProcChanceTestHelper.h" +#include "ProcEventInfoHelper.h" +#include "gtest/gtest.h" + +using namespace testing; + +class SpellProcTriggeredFilterTest : public ::testing::Test +{ +protected: + void SetUp() override {} + + // Helper to create default proc entry + SpellProcEntry CreateBasicProcEntry() + { + return SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithChance(100.0f) + .Build(); + } +}; + +// ============================================================================= +// Self-Loop Prevention Tests - SpellAuras.cpp:2191-2192 +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, SelfLoop_BlocksWhenTriggeredBySameAura) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.triggeredByAuraSpellId = 12345; // Same as proc aura + config.procAuraSpellId = 12345; + + auto procEntry = CreateBasicProcEntry(); + + // Self-loop should be blocked + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Self-loop should block proc"; +} + +TEST_F(SpellProcTriggeredFilterTest, SelfLoop_AllowsWhenTriggeredByDifferentAura) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.triggeredByAuraSpellId = 12345; // Different from proc aura + config.procAuraSpellId = 67890; + config.auraHasCanProcFromProcs = true; // Allow triggered spells + + auto procEntry = CreateBasicProcEntry(); + + // Different aura should be allowed + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Different aura trigger should allow proc"; +} + +TEST_F(SpellProcTriggeredFilterTest, SelfLoop_AllowsWhenNotTriggered) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = false; // Not a triggered spell + config.triggeredByAuraSpellId = 0; + config.procAuraSpellId = 12345; + + auto procEntry = CreateBasicProcEntry(); + + // Non-triggered spell should be allowed + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Non-triggered spell should allow proc"; +} + +// ============================================================================= +// Triggered Spell Blocking - Default Behavior +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, TriggeredSpell_BlockedByDefault) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + // No TRIGGERED_CAN_PROC attribute + auto procEntry = CreateBasicProcEntry(); + + // Should be blocked - no exceptions apply + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Triggered spell should be blocked by default"; +} + +TEST_F(SpellProcTriggeredFilterTest, NonTriggeredSpell_AllowedByDefault) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = false; // Not triggered + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + auto procEntry = CreateBasicProcEntry(); + + // Should be allowed + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Non-triggered spell should be allowed"; +} + +// ============================================================================= +// SPELL_ATTR3_CAN_PROC_FROM_PROCS Exception +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, CanProcFromProcs_AllowsTriggeredSpells) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = true; // Exception: aura has SPELL_ATTR3_CAN_PROC_FROM_PROCS + config.spellHasNotAProc = false; + + auto procEntry = CreateBasicProcEntry(); + + // Should be allowed due to aura attribute + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "SPELL_ATTR3_CAN_PROC_FROM_PROCS should allow triggered spells"; +} + +// ============================================================================= +// PROC_ATTR_TRIGGERED_CAN_PROC Exception +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, TriggeredCanProcAttribute_AllowsTriggeredSpells) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + // Set TRIGGERED_CAN_PROC attribute + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) + .WithChance(100.0f) + .Build(); + + // Should be allowed due to proc entry attribute + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "PROC_ATTR_TRIGGERED_CAN_PROC should allow triggered spells"; +} + +// ============================================================================= +// SPELL_ATTR3_NOT_A_PROC Exception +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, NotAProc_AllowsTriggeredSpell) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = true; // Exception: spell has SPELL_ATTR3_NOT_A_PROC + + auto procEntry = CreateBasicProcEntry(); + + // Should be allowed due to spell attribute + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "SPELL_ATTR3_NOT_A_PROC should allow triggered spell"; +} + +// ============================================================================= +// AUTO_ATTACK_PROC_FLAG_MASK Exception +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, AutoAttackMelee_AllowsTriggeredSpells) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + auto procEntry = CreateBasicProcEntry(); + + // Event mask includes auto-attack - exception applies + uint32 autoAttackEvent = PROC_FLAG_DONE_MELEE_AUTO_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, autoAttackEvent)) + << "AUTO_ATTACK_PROC_FLAG_MASK (melee) should allow triggered spells"; +} + +TEST_F(SpellProcTriggeredFilterTest, AutoAttackRanged_AllowsTriggeredSpells) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + auto procEntry = CreateBasicProcEntry(); + + // Hunter auto-shot or wand (ranged auto-attack) + uint32 rangedAutoEvent = PROC_FLAG_DONE_RANGED_AUTO_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, rangedAutoEvent)) + << "AUTO_ATTACK_PROC_FLAG_MASK (ranged) should allow triggered spells"; +} + +TEST_F(SpellProcTriggeredFilterTest, TakenAutoAttack_AllowsTriggeredSpells) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + auto procEntry = CreateBasicProcEntry(); + + // Taken melee auto-attack + uint32 takenMeleeEvent = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK; + + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, takenMeleeEvent)) + << "TAKEN_MELEE_AUTO_ATTACK should allow triggered spells"; +} + +// ============================================================================= +// Combined Scenarios +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, Combined_SelfLoopTakesPrecedence) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.triggeredByAuraSpellId = 12345; + config.procAuraSpellId = 12345; // Self-loop + config.auraHasCanProcFromProcs = true; // Would normally allow + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) + .WithChance(100.0f) + .Build(); + + // Self-loop should still block even with exceptions + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Self-loop should block even when TRIGGERED_CAN_PROC is set"; +} + +TEST_F(SpellProcTriggeredFilterTest, Combined_MultipleExceptions) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = true; // Exception 1 + config.spellHasNotAProc = true; // Exception 2 + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) // Exception 3 + .WithChance(100.0f) + .Build(); + + // Should be allowed (multiple exceptions all pass) + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Multiple exceptions should still allow proc"; +} + +// ============================================================================= +// Real Spell Scenarios +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, Scenario_HotStreak_TriggeredPyroblast) +{ + // Hot Streak (48108) allows triggered Pyroblast to not proc it again + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; // Pyroblast was triggered by Hot Streak + config.triggeredByAuraSpellId = 48108; // Hot Streak + config.procAuraSpellId = 48108; // Hot Streak is checking if it should proc + + auto procEntry = CreateBasicProcEntry(); + + // Self-loop: Hot Streak can't proc from spell it triggered + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "Hot Streak triggered Pyroblast should not proc Hot Streak"; +} + +TEST_F(SpellProcTriggeredFilterTest, Scenario_SwordSpec_ChainProcs) +{ + // Sword Specialization with TRIGGERED_CAN_PROC + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.triggeredByAuraSpellId = 12345; // Some other proc + config.procAuraSpellId = 16459; // Sword Specialization + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithAttributesMask(PROC_ATTR_TRIGGERED_CAN_PROC) + .WithChance(5.0f) + .Build(); + + // TRIGGERED_CAN_PROC allows chain procs (but not self-loops) + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_MELEE_AUTO_ATTACK)) + << "Sword Spec with TRIGGERED_CAN_PROC should allow chain procs"; +} + +TEST_F(SpellProcTriggeredFilterTest, Scenario_WindfuryWeapon_AutoAttack) +{ + // Windfury Weapon procs from auto-attacks, which are allowed for triggered spells + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; // Windfury extra attacks are triggered + config.auraHasCanProcFromProcs = false; + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK) + .WithProcsPerMinute(2.0f) + .Build(); + + // Auto-attack exception allows triggered Windfury attacks + EXPECT_FALSE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_MELEE_AUTO_ATTACK)) + << "Windfury triggered attacks should be allowed (auto-attack exception)"; +} + +// ============================================================================= +// Edge Cases +// ============================================================================= + +TEST_F(SpellProcTriggeredFilterTest, EdgeCase_ZeroEventMask) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + + auto procEntry = CreateBasicProcEntry(); + + // Zero event mask means no auto-attack exception + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, 0)) + << "Zero event mask should not grant auto-attack exception"; +} + +TEST_F(SpellProcTriggeredFilterTest, EdgeCase_AllExceptionsDisabled) +{ + ProcChanceTestHelper::TriggeredSpellConfig config; + config.isTriggered = true; + config.auraHasCanProcFromProcs = false; + config.spellHasNotAProc = false; + + auto procEntry = SpellProcEntryBuilder() + .WithProcFlags(PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) + .WithAttributesMask(0) // No TRIGGERED_CAN_PROC + .WithChance(100.0f) + .Build(); + + // No exceptions - should block + EXPECT_TRUE(ProcChanceTestHelper::ShouldBlockTriggeredSpell( + config, procEntry, PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)) + << "No exceptions should block triggered spell"; +} diff --git a/src/test/server/game/Spells/SpellScriptMissileBarrageTest.cpp b/src/test/server/game/Spells/SpellScriptMissileBarrageTest.cpp new file mode 100644 index 000000000..bde844c19 --- /dev/null +++ b/src/test/server/game/Spells/SpellScriptMissileBarrageTest.cpp @@ -0,0 +1,274 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellScriptMissileBarrageTest.cpp + * @brief Unit tests for Missile Barrage (44404-44408) proc behavior + * + * Missile Barrage talent should proc: + * - 100% chance when casting Arcane Blast (SpellFamilyFlags[0] & 0x20000000) + * - 50% reduced chance when casting other spells (Arcane Barrage, Frostfire Bolt, etc.) + * + * DBC Base proc chances by rank: + * - Rank 1 (44404): 4% + * - Rank 2 (44405): 8% + * - Rank 3 (44406): 12% + * - Rank 4 (44407): 16% + * - Rank 5 (44408): 20% + * + * Effective proc rates: + * - Arcane Blast: Full DBC chance (4-20%) + * - Other spells: 50% of DBC chance (2-10%) + */ + +#include "gtest/gtest.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "SharedDefines.h" + +// ============================================================================= +// Missile Barrage Script Logic Simulation +// ============================================================================= + +/** + * @brief Simulates the CheckProc logic from spell_mage_missile_barrage + * + * This mirrors the actual script at: + * src/server/scripts/Spells/spell_mage.cpp:1325-1338 + * + * @param spellFamilyFlags0 The SpellFamilyFlags[0] of the triggering spell + * @param rollResult The result of roll_chance_i(50) - pass 0-49 to succeed, 50-99 to fail + * @return true if the proc check passes + */ +bool SimulateMissileBarrageCheckProc(uint32 spellFamilyFlags0, int rollResult) +{ + // Arcane Blast - full proc chance (100%) + // Arcane Blast spell family flags: 0x20000000 + if (spellFamilyFlags0 & 0x20000000) + return true; + + // Other spells - 50% proc chance + // Simulates: return roll_chance_i(50); + return rollResult < 50; +} + +/** + * @brief Get the SpellFamilyFlags[0] for common Mage spells + */ +namespace MageSpellFlags +{ + constexpr uint32 ARCANE_BLAST = 0x20000000; + constexpr uint32 ARCANE_MISSILES = 0x00000020; + constexpr uint32 FIREBALL = 0x00000001; + constexpr uint32 FROSTFIRE_BOLT = 0x00000000; // Uses SpellFamilyFlags[1] + constexpr uint32 ARCANE_BARRAGE = 0x00000000; // Uses SpellFamilyFlags[1] +} + +// ============================================================================= +// Test Fixture +// ============================================================================= + +class MissileBarrageTest : public ::testing::Test +{ +protected: + void SetUp() override {} + void TearDown() override {} + + /** + * @brief Run multiple proc checks and return the success rate + * @param spellFamilyFlags0 The spell flags to test + * @param iterations Number of iterations + * @return Success rate as percentage (0-100) + */ + float RunStatisticalTest(uint32 spellFamilyFlags0, int iterations = 10000) + { + int successes = 0; + for (int i = 0; i < iterations; i++) + { + // Simulate random roll 0-99 + int roll = i % 100; + if (SimulateMissileBarrageCheckProc(spellFamilyFlags0, roll)) + successes++; + } + return (float)successes / iterations * 100.0f; + } +}; + +// ============================================================================= +// Deterministic Tests - Arcane Blast +// ============================================================================= + +TEST_F(MissileBarrageTest, ArcaneBlast_AlwaysProcs_RegardlessOfRoll) +{ + // Arcane Blast should always pass CheckProc, regardless of the roll result + for (int roll = 0; roll < 100; roll++) + { + EXPECT_TRUE(SimulateMissileBarrageCheckProc(MageSpellFlags::ARCANE_BLAST, roll)) + << "Arcane Blast should always proc, but failed with roll=" << roll; + } +} + +TEST_F(MissileBarrageTest, ArcaneBlast_Returns100PercentRate) +{ + float rate = RunStatisticalTest(MageSpellFlags::ARCANE_BLAST); + EXPECT_NEAR(rate, 100.0f, 0.01f) << "Arcane Blast should have 100% CheckProc pass rate"; +} + +// ============================================================================= +// Deterministic Tests - Other Spells (50% Reduction) +// ============================================================================= + +TEST_F(MissileBarrageTest, Fireball_ProcsOnLowRoll) +{ + // Rolls 0-49 should succeed + for (int roll = 0; roll < 50; roll++) + { + EXPECT_TRUE(SimulateMissileBarrageCheckProc(MageSpellFlags::FIREBALL, roll)) + << "Fireball should proc with roll=" << roll << " (< 50)"; + } +} + +TEST_F(MissileBarrageTest, Fireball_FailsOnHighRoll) +{ + // Rolls 50-99 should fail + for (int roll = 50; roll < 100; roll++) + { + EXPECT_FALSE(SimulateMissileBarrageCheckProc(MageSpellFlags::FIREBALL, roll)) + << "Fireball should NOT proc with roll=" << roll << " (>= 50)"; + } +} + +TEST_F(MissileBarrageTest, Fireball_Returns50PercentRate) +{ + float rate = RunStatisticalTest(MageSpellFlags::FIREBALL); + EXPECT_NEAR(rate, 50.0f, 0.01f) << "Fireball should have 50% CheckProc pass rate"; +} + +TEST_F(MissileBarrageTest, ArcaneMissiles_Returns50PercentRate) +{ + float rate = RunStatisticalTest(MageSpellFlags::ARCANE_MISSILES); + EXPECT_NEAR(rate, 50.0f, 0.01f) << "Arcane Missiles should have 50% CheckProc pass rate"; +} + +TEST_F(MissileBarrageTest, OtherSpells_Returns50PercentRate) +{ + // Any spell that doesn't have the Arcane Blast flag should get 50% rate + float rate = RunStatisticalTest(0x00000000); + EXPECT_NEAR(rate, 50.0f, 0.01f) << "Other spells should have 50% CheckProc pass rate"; +} + +// ============================================================================= +// Effective Proc Rate Tests +// ============================================================================= + +/** + * @brief Calculate the effective proc rate combining DBC chance and CheckProc + * @param dbcChance Base proc chance from DBC (e.g., 20 for rank 5) + * @param checkProcRate CheckProc pass rate (100 for Arcane Blast, 50 for others) + * @return Effective proc rate as percentage + */ +float CalculateEffectiveProcRate(float dbcChance, float checkProcRate) +{ + return dbcChance * (checkProcRate / 100.0f); +} + +TEST_F(MissileBarrageTest, EffectiveRate_ArcaneBlast_Rank5) +{ + // Rank 5: 20% base chance * 100% CheckProc = 20% effective + float effective = CalculateEffectiveProcRate(20.0f, 100.0f); + EXPECT_NEAR(effective, 20.0f, 0.01f); +} + +TEST_F(MissileBarrageTest, EffectiveRate_Fireball_Rank5) +{ + // Rank 5: 20% base chance * 50% CheckProc = 10% effective + float effective = CalculateEffectiveProcRate(20.0f, 50.0f); + EXPECT_NEAR(effective, 10.0f, 0.01f); +} + +TEST_F(MissileBarrageTest, EffectiveRate_ArcaneBlast_Rank1) +{ + // Rank 1: 4% base chance * 100% CheckProc = 4% effective + float effective = CalculateEffectiveProcRate(4.0f, 100.0f); + EXPECT_NEAR(effective, 4.0f, 0.01f); +} + +TEST_F(MissileBarrageTest, EffectiveRate_Fireball_Rank1) +{ + // Rank 1: 4% base chance * 50% CheckProc = 2% effective + float effective = CalculateEffectiveProcRate(4.0f, 50.0f); + EXPECT_NEAR(effective, 2.0f, 0.01f); +} + +// ============================================================================= +// DBC Data Validation +// ============================================================================= + +TEST_F(MissileBarrageTest, DBCProcChances_MatchExpectedValues) +{ + // Expected DBC proc chances for each rank + // Note: These should match the actual DBC values + struct RankData + { + uint32 spellId; + int expectedChance; + }; + + std::vector ranks = { + { 44404, 4 }, // Rank 1: 4% (actually 8% in some versions) + { 44405, 8 }, // Rank 2 + { 44406, 12 }, // Rank 3 + { 44407, 16 }, // Rank 4 + { 44408, 20 }, // Rank 5 + }; + + // This documents the expected values - actual verification would require SpellMgr + for (auto const& rank : ranks) + { + SCOPED_TRACE("Spell ID: " + std::to_string(rank.spellId)); + // The actual DBC lookup would be: + // SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(rank.spellId); + // EXPECT_EQ(spellInfo->ProcChance, rank.expectedChance); + } +} + +// ============================================================================= +// Boundary Tests +// ============================================================================= + +TEST_F(MissileBarrageTest, BoundaryRoll_49_Succeeds) +{ + // Roll of 49 should succeed (< 50) + EXPECT_TRUE(SimulateMissileBarrageCheckProc(MageSpellFlags::FIREBALL, 49)); +} + +TEST_F(MissileBarrageTest, BoundaryRoll_50_Fails) +{ + // Roll of 50 should fail (>= 50) + EXPECT_FALSE(SimulateMissileBarrageCheckProc(MageSpellFlags::FIREBALL, 50)); +} + +TEST_F(MissileBarrageTest, ArcaneBlastFlag_ExactMatch) +{ + // Test that exactly the Arcane Blast flag triggers 100% rate + EXPECT_TRUE(SimulateMissileBarrageCheckProc(0x20000000, 99)); + + // Combined flags should also work if Arcane Blast is present + EXPECT_TRUE(SimulateMissileBarrageCheckProc(0x20000001, 99)); + EXPECT_TRUE(SimulateMissileBarrageCheckProc(0x20000020, 99)); + EXPECT_TRUE(SimulateMissileBarrageCheckProc(0xFFFFFFFF, 99)); +}