diff options
40 files changed, 1549 insertions, 58 deletions
diff --git a/.travis.yml b/.travis.yml index cddb98492a2..ab2b1a0998c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +sudo: required +dist: trusty + language: cpp compiler: - clang @@ -6,16 +9,11 @@ git: depth: 1 before_install: - - echo "yes" | sudo add-apt-repository ppa:george-edison55/precise-backports - - echo "yes" | sudo add-apt-repository ppa:boost-latest/ppa - - echo "yes" | sudo add-apt-repository ppa:ubuntu-toolchain-r/test - - echo "yes" | sudo add-apt-repository 'deb http://llvm.org/apt/precise/ llvm-toolchain-precise-3.5 main' - - wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add - - sudo apt-get -qq update - - sudo apt-get -qq install build-essential libtool gcc-4.8 g++-4.8 make cmake cmake-data openssl clang-3.5 + - sudo apt-get -qq install build-essential libtool make cmake cmake-data openssl - sudo apt-get -qq install libssl-dev libmysqlclient-dev libmysql++-dev libreadline6-dev zlib1g-dev libbz2-dev - sudo apt-get -qq install libboost1.55-dev libboost-thread1.55-dev libboost-filesystem1.55-dev libboost-system1.55-dev libboost-program-options1.55-dev libboost-iostreams1.55-dev - - export CC=clang-3.5 CXX=clang++-3.5 + - sudo apt-get -qq install mysql-server - git config user.email "travis@build.bot" && git config user.name "Travis CI" - git tag -a -m "Travis build" init diff --git a/sql/updates/world/3.3.5/2016_05_30_09_world.sql b/sql/updates/world/3.3.5/2016_05_30_09_world.sql new file mode 100644 index 00000000000..3ec588d0982 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_30_09_world.sql @@ -0,0 +1,5 @@ +UPDATE `quest_template_addon` SET `SpecialFlags`=2 WHERE `ID`=1144; +UPDATE `creature_template` SET `flags_extra`=64 WHERE `entry` IN(28111,28112,28078,28079,23667, 23668, 32669,28844,28843,24199,24198,23564); +UPDATE `reference_loot_template` SET `GroupId`=2 WHERE `Entry`=34203 AND `Item`=43959; +UPDATE `smart_scripts` SET `target_type`=12, `target_param1`=1 WHERE `entryorguid`=2735500 AND `source_type`=9 AND `id`=5 AND `link`=0; +UPDATE `gameobject` SET `position_x`=5445.057129, `position_y`=4908.831543, `position_z`=-189.508224 WHERE `guid`=99748; diff --git a/sql/updates/world/3.3.5/2016_05_30_10_world.sql b/sql/updates/world/3.3.5/2016_05_30_10_world.sql new file mode 100644 index 00000000000..b8dbe0b94a6 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_30_10_world.sql @@ -0,0 +1,26 @@ +DELETE FROM `gameobject_loot_template` WHERE `Entry`=26782; +INSERT INTO `gameobject_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES +(26782, 44724, 0, 100, 0, 1, 1, 1, 1, NULL), -- Everfrost Chip +(26782, 44725, 0, 19, 0, 1, 2, 1, 1, NULL), -- Everfrost Chip (Quest Starter) +(26782, 44729, 0, 81, 0, 1, 2, 1, 1, NULL); -- Everfrost Powder + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=4 AND `SourceGroup`=26782; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(4, 26782, 44724, 0, 1, 8, 0, 13420, 0, 0, 0, 0, 0, '', NULL), -- Everfrost Chip only drops if player is rewarded for ever frost, +(4, 26782, 44729, 0, 0, 9, 0, 13420, 0, 0, 1, 0, 0, '', NULL), -- Everfrost powder only drops if player has not taken or completed Everfrost, +(4, 26782, 44729, 0, 0, 8, 0, 13420, 0, 0, 1, 0, 0, '', NULL), -- Everfrost powder only drops if player has not taken or completed Everfrost, +(4, 26782, 44725, 0, 0, 9, 0, 13420, 0, 0, 1, 0, 0, '', NULL), -- Everfrost Chip (Quest Starter) only drops if player has not taken or completed Everfrost, +(4, 26782, 44725, 0, 0, 8, 0, 13420, 0, 0, 1, 0, 0, '', NULL), -- Everfrost Chip (Quest Starter) only drops if player has not taken or completed Everfrost, +(4, 26782, 44725, 0, 0, 5, 0, 1119, 8, 0, 0, 0, 0, '', NULL); -- Everfrost Chip (Quest Starter) only drops if player is friendly with Sons of Hodir, + +UPDATE `quest_template_addon` SET `SpecialFlags`=1 WHERE `ID`=13421; + +DELETE FROM `creature_questender` WHERE `quest`=13421; +INSERT INTO `creature_questender` (`id`, `quest`) VALUES +(32594, 13421); + +DELETE FROM `creature_queststarter` WHERE `quest`=13421; +INSERT INTO `creature_queststarter` (`id`, `quest`) VALUES +(32594, 13421); + +UPDATE `quest_template_addon` SET `PrevQuestID`=13420 WHERE `ID`=13421; diff --git a/sql/updates/world/3.3.5/2016_05_31_00_world.sql b/sql/updates/world/3.3.5/2016_05_31_00_world.sql new file mode 100644 index 00000000000..29a29413167 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_00_world.sql @@ -0,0 +1,6 @@ +-- +UPDATE `creature` SET `position_x`= -3739.09, `position_y`= 5405.21, `position_z`=-3.33350, `orientation`=2.70103, `MovementType`=0, `spawndist`=0 WHERE `guid`=78837 AND `id`=22464; +UPDATE `creature` SET `position_x`= -3743.46, `position_y`= 5403.87, `position_z`=-3.33635, `orientation`=1.16950, `MovementType`=0, `spawndist`=0 WHERE `guid`=78838 AND `id`=22464; +UPDATE `creature` SET `position_x`= -3746.42, `position_y`= 5405.33, `position_z`=-3.33737, `orientation`=0.39587, `MovementType`=0, `spawndist`=0 WHERE `guid`=78839 AND `id`=22464; +UPDATE `creature` SET `position_x`= -3750.65, `position_y`= 5408.94, `position_z`=-3.33391, `orientation`=2.54786, `MovementType`=0, `spawndist`=0 WHERE `guid`=78840 AND `id`=22464; +UPDATE `creature` SET `position_x`= -3741.11, `position_y`= 5403.61, `position_z`=-3.33493, `orientation`=1.9117 WHERE `guid`=78818 AND `id`=22458; diff --git a/sql/updates/world/3.3.5/2016_05_31_01_world_335.sql b/sql/updates/world/3.3.5/2016_05_31_01_world_335.sql new file mode 100644 index 00000000000..2d92a9fc537 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_01_world_335.sql @@ -0,0 +1,9 @@ +-- +DELETE FROM `conditions` WHERE `SourceEntry` IN (6545, 6546, 6547) AND `SourceTypeOrReferenceId` IN (19, 20); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(19, 0, 6545, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Runner Update' - Can accept if player has quest 'The Warsong Reports' in quest log"), +(20, 0, 6545, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Runner Update' - Can accept if player has quest 'The Warsong Reports' in quest log"), +(19, 0, 6546, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Outrider Update' - Can accept if player has quest 'The Warsong Reports' in quest log"), +(20, 0, 6546, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Outrider Update' - Can accept if player has quest 'The Warsong Reports' in quest log"), +(19, 0, 6547, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Scout Update' - Can accept if player has quest 'The Warsong Reports' in quest log"), +(20, 0, 6547, 0, 0, 9, 0, 6543, 0, 0, 0, 0, 0, "", "Quest 'Warsong Scout Update' - Can accept if player has quest 'The Warsong Reports' in quest log"); diff --git a/sql/updates/world/3.3.5/2016_05_31_02_world.sql b/sql/updates/world/3.3.5/2016_05_31_02_world.sql new file mode 100644 index 00000000000..09674385ab4 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_02_world.sql @@ -0,0 +1,19 @@ +-- +UPDATE `creature` SET `spawndist`=0, `MovementType`=0, `unit_flags`=537133824 WHERE `guid` IN (103024, 103025, 103026, 103027, 103028); + +DELETE FROM `creature_addon` WHERE `guid` IN (103024, 103025, 103026, 103027, 103028); +INSERT INTO `creature_addon` (`guid`, `mount`, `bytes1`, `bytes2`, `auras`) VALUES +(103024, 0, 0x0, 0x1, '29266'), -- 25680 - 29266 - 29266 +(103025, 0, 0x0, 0x1, '29266'), -- 25680 - 29266 - 29266 +(103026, 0, 0x0, 0x1, '29266'), -- 25680 - 29266 - 29266 +(103027, 0, 0x0, 0x1, '29266'), -- 25680 - 29266 - 29266 +(103028, 0, 0x0, 0x1, '29266'); -- 25680 - 29266 - 29266 + +UPDATE `creature` SET `spawndist`=0, `MovementType`=0 WHERE `id`=29546; + +DELETE FROM `creature_template_addon` WHERE `entry` IN (24196, 29546); +INSERT INTO `creature_template_addon` (`entry`, `mount`, `bytes1`, `bytes2`, `auras`) VALUES +(24196, 0, 0x0, 0x1, '29266'), -- 24196 - 29266 +(29546, 0, 0x0, 0x1, '29266'); -- 29546 - 29266 + +DELETE FROM `creature_addon` WHERE `guid`=105491; diff --git a/sql/updates/world/3.3.5/2016_05_31_03_world.sql b/sql/updates/world/3.3.5/2016_05_31_03_world.sql new file mode 100644 index 00000000000..60c07d15735 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_03_world.sql @@ -0,0 +1,3 @@ +-- +UPDATE `conditions` SET `ConditionValue1`=11248, `Comment`='Winterskorn mobs drop item 33314 only if Quest 11248 has been rewarded' WHERE `SourceTypeOrReferenceId`=1 AND `SourceEntry`=33314; +UPDATE `conditions` SET `ConditionValue1`=11256, `Comment`='Winterskorn mobs drop item 33345 only if Quest 11256 has been rewarded' WHERE `SourceTypeOrReferenceId`=1 AND `SourceEntry`=33345; diff --git a/sql/updates/world/3.3.5/2016_05_31_04_world.sql b/sql/updates/world/3.3.5/2016_05_31_04_world.sql new file mode 100644 index 00000000000..d3ddfcfa05c --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_04_world.sql @@ -0,0 +1,15 @@ +-- +DELETE FROM `areatrigger_scripts` WHERE `entry` IN (5187, 5190); +INSERT INTO `areatrigger_scripts` VALUES +(5187, "SmartTrigger"), +(5190, "SmartTrigger"); + +DELETE FROM `smart_scripts` WHERE `entryorguid` IN (5187, 5190) AND `source_type`=2; +INSERT INTO `smart_scripts` VALUES +(5187, 2, 0, 0, 46, 0, 100, 0, 0, 0, 0, 0, 62, 571, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6150.7563, -1071.3817, 402.7154, 2.1916, "Areatrigger - On Trigger - Teleport"), +(5190, 2, 0, 0, 46, 0, 100, 0, 0, 0, 0, 0, 62, 571, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6314.1860, -1758.2946, 457.0714, 1.6787, "Areatrigger - On Trigger - Teleport"); + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=22 AND `SourceId`=2 AND SourceEntry IN (5187, 5190); +INSERT INTO `conditions` VALUES +(22, 1, 5187, 2, 0, 8, 0, 12821, 0, 0, 0, 0, 0, "", "Event requires quest rewarded"), +(22, 1, 5190, 2, 0, 8, 0, 12821, 0, 0, 0, 0, 0, "", "Event requires quest rewarded"); diff --git a/sql/updates/world/3.3.5/2016_05_31_05_world.sql b/sql/updates/world/3.3.5/2016_05_31_05_world.sql new file mode 100644 index 00000000000..18bbf61baa4 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_05_world.sql @@ -0,0 +1,12 @@ +-- +UPDATE `quest_template_addon` SET `PrevQuestID`=0 WHERE `Id`=12836; + +DELETE FROM `conditions` WHERE `SourceEntry`=12828 AND `SourceTypeOrReferenceId` IN (19, 20); +INSERT INTO `conditions` VALUES +(19, 0, 12828, 0, 0, 8, 0, 12827, 0, 0, 0, 0, 0, "", "Show mark for quest 'Ample Inspiration' only if quest 'Reclaimed Rations' is rewarded"), +(20, 0, 12828, 0, 0, 8, 0, 12827, 0, 0, 0, 0, 0, "", "Can accept quest 'Ample Inspiration' only if quest 'Reclaimed Rations' is rewarded"); + +DELETE FROM `quest_template_addon` WHERE `ID` IN (12862, 13060); +INSERT INTO `quest_template_addon` (`ID`, `PrevQuestId`) VALUES +(12862, 12824), +(13060, 12824); diff --git a/sql/updates/world/3.3.5/2016_05_31_06_world.sql b/sql/updates/world/3.3.5/2016_05_31_06_world.sql new file mode 100644 index 00000000000..46d8b875c82 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_06_world.sql @@ -0,0 +1,10 @@ +-- +-- remove mis-spawned Mistbat (entry: 16353) +DELETE FROM `creature` WHERE `guid`=57016; + +-- remove one too many spawned Elder Springpaw (entry: 15651) +DELETE FROM `creature` WHERE `guid`=55946; + +-- set random movement and spawn distance to the common value for that creature +UPDATE `creature` SET `spawndist`=5, `MovementType`=1 WHERE `guid`=55942; +UPDATE `creature` SET `spawndist`=5, `MovementType`=1 WHERE `guid`=55945; diff --git a/sql/updates/world/3.3.5/2016_05_31_07_world.sql b/sql/updates/world/3.3.5/2016_05_31_07_world.sql new file mode 100644 index 00000000000..7ed017ff1cd --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_07_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_loot_template` SET `MinCount`=1, `MaxCount`=1 WHERE `Item`=11509; diff --git a/sql/updates/world/3.3.5/2016_05_31_08_world.sql b/sql/updates/world/3.3.5/2016_05_31_08_world.sql new file mode 100644 index 00000000000..2cf3fbcdfc4 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_08_world.sql @@ -0,0 +1,13 @@ +-- +DELETE FROM `conditions` WHERE `SourceEntry`=23359 AND `SourceTypeOrReferenceId`=17; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`,`SourceGroup`,`SourceEntry`,`SourceId`,`ElseGroup`,`ConditionTypeOrReference`,`ConditionTarget`,`ConditionValue1`,`ConditionValue2`,`ConditionValue3`,`NegativeCondition`, `ErrorType`, `ErrorTextId`,`ScriptName`,`Comment`) VALUES +(17, 0, 23359, 0, 0, 31, 1, 3, 5357, 0, 0, 0, 0, "", "Transmogrify! target can be Land Walker"), +(17, 0, 23359, 0, 0, 36, 1, 0, 0, 0, 0, 0, 0, "", "Transmogrify! target must be alive"), +(17, 0, 23359, 0, 1, 31, 1, 3, 5358, 0, 0, 0, 0, "", "Transmogrify! target can be Cliff Giant"), +(17, 0, 23359, 0, 1, 36, 1, 0, 0, 0, 0, 0, 0, "", "Transmogrify! target must be alive"), +(17, 0, 23359, 0, 2, 31, 1, 3, 5359, 0, 0, 0, 0, "", "Transmogrify! target can be Shore Strider"), +(17, 0, 23359, 0, 2, 36, 1, 0, 0, 0, 0, 0, 0, "", "Transmogrify! target must be alive"), +(17, 0, 23359, 0, 3, 31, 1, 3, 5360, 0, 0, 0, 0, "", "Transmogrify! target can be Deep Strider"), +(17, 0, 23359, 0, 3, 36, 1, 0, 0, 0, 0, 0, 0, "", "Transmogrify! target must be alive"), +(17, 0, 23359, 0, 4, 31, 1, 3, 5361, 0, 0, 0, 0, "", "Transmogrify! target can be Wave Strider"), +(17, 0, 23359, 0, 4, 36, 1, 0, 0, 0, 0, 0, 0, "", "Transmogrify! target must be alive"); diff --git a/sql/updates/world/3.3.5/2016_05_31_09_world.sql b/sql/updates/world/3.3.5/2016_05_31_09_world.sql new file mode 100644 index 00000000000..73d62a92772 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_09_world.sql @@ -0,0 +1,3 @@ +-- +UPDATE `creature_template` SET `speed_run`=0.42857 WHERE `entry` IN (16027); +UPDATE `smart_scripts` SET `action_param1`=22 WHERE `entryorguid`=16027 AND `source_type`=0 AND `id`=3 AND `link`=4; diff --git a/sql/updates/world/3.3.5/2016_05_31_10_world.sql b/sql/updates/world/3.3.5/2016_05_31_10_world.sql new file mode 100644 index 00000000000..e7536b3a51a --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_10_world.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `creature_loot_template` WHERE `Item`=31245; diff --git a/sql/updates/world/3.3.5/2016_05_31_11_world.sql b/sql/updates/world/3.3.5/2016_05_31_11_world.sql new file mode 100644 index 00000000000..e5d35f5eb42 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_11_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `reference_loot_template` SET `Item`=49667, `Chance`=10 WHERE `Item`=48679; diff --git a/sql/updates/world/3.3.5/2016_05_31_12_world_335.sql b/sql/updates/world/3.3.5/2016_05_31_12_world_335.sql new file mode 100644 index 00000000000..b20c38b36ba --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_12_world_335.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template_addon` SET `PrevQuestID`=155 WHERE `ID`=214; diff --git a/sql/updates/world/3.3.5/2016_05_31_13_world.sql b/sql/updates/world/3.3.5/2016_05_31_13_world.sql new file mode 100644 index 00000000000..6dd3d984c1c --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_13_world.sql @@ -0,0 +1,23 @@ +-- +UPDATE `quest_template_addon` SET `ExclusiveGroup`=8747 WHERE `Id` IN (8747, 8752, 8757); + +UPDATE `quest_template_addon` SET `PrevQuestId`=0, `SpecialFlags`=1 WHERE `Id` IN (8764, 8765, 8766); + +DELETE FROM `conditions` WHERE `SourceEntry` IN (8764, 8765, 8766); +INSERT INTO `conditions` VALUES +(19, 0, 8764, 0, 0, 2, 0, 21200, 1, 1, 0, 0, 0, "", "Quest 8764 is available only if player has item 21200 in bags or bank"), +(20, 0, 8764, 0, 0, 2, 0, 21200, 1, 1, 0, 0, 0, "", "Show question mark for quest 8764 only if player has item 21200 in bags or bank"), + +(19, 0, 8765, 0, 0, 2, 0, 21210, 1, 1, 0, 0, 0, "", "Quest 8765 is available only if player has item 21210 in bags or bank"), +(20, 0, 8765, 0, 0, 2, 0, 21210, 1, 1, 0, 0, 0, "", "Show question mark for quest 8765 only if player has item 21210 in bags or bank"), + +(19, 0, 8766, 0, 0, 2, 0, 21205, 1, 1, 0, 0, 0, "", "Quest 8766 is available only if player has item 21205 in bags or bank"), +(20, 0, 8766, 0, 0, 2, 0, 21205, 1, 1, 0, 0, 0, "", "Show question mark for quest 8766 only if player has item 21205 in bags or bank"); + +DELETE FROM `quest_offer_reward` WHERE `ID` IN (8747, 8752, 8757); +INSERT INTO `quest_offer_reward` (`ID`,`Emote1`,`Emote2`,`Emote3`,`Emote4`,`EmoteDelay1`,`EmoteDelay2`,`EmoteDelay3`,`EmoteDelay4`,`RewardText`,`VerifiedBuild`) VALUES +(8747,1,0,0,0,0,0,0,0,(SELECT `CompletionText` FROM `quest_request_items` WHERE `ID`=8747),12340), +(8752,1,0,0,0,0,0,0,0,(SELECT `CompletionText` FROM `quest_request_items` WHERE `ID`=8752),12340), +(8757,1,0,0,0,0,0,0,0,(SELECT `CompletionText` FROM `quest_request_items` WHERE `ID`=8757),12340); + +UPDATE `quest_request_items` SET `CompletionText` = "Never have I seen such tenacity! The Bronze Flight grants you one final enchantment. The Timeless One himself has requested it so!$B$BHand me your signet ring so that I may make the necessary adjustments." WHERE `ID` IN (8751, 8756, 8761); diff --git a/sql/updates/world/3.3.5/2016_05_31_14_world_335.sql b/sql/updates/world/3.3.5/2016_05_31_14_world_335.sql new file mode 100644 index 00000000000..496a9645bda --- /dev/null +++ b/sql/updates/world/3.3.5/2016_05_31_14_world_335.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template` SET `unit_flags`=33536 WHERE `entry`=24117; diff --git a/sql/updates/world/3.3.5/2016_06_01_00_world.sql b/sql/updates/world/3.3.5/2016_06_01_00_world.sql new file mode 100644 index 00000000000..5f4e7dbb01c --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_00_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template` SET `AllowableRaces`=1024 WHERE `ID`=9429; diff --git a/sql/updates/world/3.3.5/2016_06_01_01_world.sql b/sql/updates/world/3.3.5/2016_06_01_01_world.sql new file mode 100644 index 00000000000..80ecabe34aa --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_01_world.sql @@ -0,0 +1,5 @@ +-- +UPDATE `creature` SET `position_x`=3154.581, `position_y`=-3126.18, `position_z`=293.5911, `orientation`=4.430199 WHERE `guid`=76311; +UPDATE `creature` SET `position_x`=3128.622, `position_y`=-3119.604, `position_z`=293.4113, `orientation`=4.738929 WHERE `guid`=76312; +UPDATE `creature` SET `position_x`=3175.281, `position_y`=-3134.764, `position_z`=293.4368, `orientation`=4.244924 WHERE `guid`=76313; +UPDATE `smart_scripts` SET `action_param1`=34 WHERE `entryorguid`=16027 AND `source_type`=0 AND `id`=3 AND `link`=4; diff --git a/sql/updates/world/3.3.5/2016_06_01_02_world.sql b/sql/updates/world/3.3.5/2016_06_01_02_world.sql new file mode 100644 index 00000000000..e2c27b15339 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_02_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template_addon` SET `PrevQuestID`=985 WHERE `ID`=986; diff --git a/sql/updates/world/3.3.5/2016_06_01_03_world.sql b/sql/updates/world/3.3.5/2016_06_01_03_world.sql new file mode 100644 index 00000000000..54beb26b15c --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_03_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template_addon` SET `AllowableClasses`=256 WHERE `ID`=397; diff --git a/sql/updates/world/3.3.5/2016_06_01_04_world.sql b/sql/updates/world/3.3.5/2016_06_01_04_world.sql new file mode 100644 index 00000000000..1de1716102b --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_04_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template` SET `AllowableRaces`=512 WHERE `ID`=9425; diff --git a/sql/updates/world/3.3.5/2016_06_01_05_world.sql b/sql/updates/world/3.3.5/2016_06_01_05_world.sql new file mode 100644 index 00000000000..3d7a32ac308 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_05_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `item_loot_template` SET `Chance`=100 WHERE `Entry`=24336; diff --git a/sql/updates/world/3.3.5/2016_06_01_06_world.sql b/sql/updates/world/3.3.5/2016_06_01_06_world.sql new file mode 100644 index 00000000000..9344147e067 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_06_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template` SET `AllowableRaces`=0 WHERE `ID`=1486; diff --git a/sql/updates/world/3.3.5/2016_06_01_07_world.sql b/sql/updates/world/3.3.5/2016_06_01_07_world.sql new file mode 100644 index 00000000000..9c159c54cb6 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_07_world.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `creature_queststarter` WHERE `quest`=249; +INSERT INTO `creature_queststarter` (`id`, `quest`) VALUES +(313, 249); diff --git a/sql/updates/world/3.3.5/2016_06_01_08_world.sql b/sql/updates/world/3.3.5/2016_06_01_08_world.sql new file mode 100644 index 00000000000..356c0561bf2 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_08_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_loot_template` SET `Chance`=100 WHERE `Item`=29795; diff --git a/sql/updates/world/3.3.5/2016_06_01_09_world.sql b/sql/updates/world/3.3.5/2016_06_01_09_world.sql new file mode 100644 index 00000000000..5cf06521a19 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_09_world.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `quest_template_addon` WHERE `ID`=768; +INSERT INTO `quest_template_addon` (`ID`, `RequiredSkillID`, `RequiredSkillPoints`) VALUES +(768, 393, 1); diff --git a/sql/updates/world/3.3.5/2016_06_01_10_world.sql b/sql/updates/world/3.3.5/2016_06_01_10_world.sql new file mode 100644 index 00000000000..abc358431e3 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_10_world.sql @@ -0,0 +1,2 @@ +-- +UPDATE `quest_template_addon` SET `SpecialFlags`=`SpecialFlags`|1 WHERE `ID` IN (961, 3375, 3382, 3483); diff --git a/sql/updates/world/3.3.5/2016_06_01_11_world.sql b/sql/updates/world/3.3.5/2016_06_01_11_world.sql new file mode 100644 index 00000000000..dc0b7b05a33 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_11_world.sql @@ -0,0 +1,81 @@ +-- +DELETE FROM `conditions` WHERE `SourceEntry`=254 AND `SourceTypeOrReferenceId`=20 AND `ConditionTypeOrReference` IN (8, 28); +INSERT INTO `conditions` VALUES +(20, 0, 254, 0, 0, 8, 0, 253, 0, 0, 1, 0, 0, "", "Show question mark for quest 'Digging Through the Dirt' only if quest 'Bride of the Embalmer' is not rewarded"), +(20, 0, 254, 0, 0, 28, 0, 253, 0, 0, 1, 0, 0, "", "Show question mark for quest 'Digging Through the Dirt' only if quest 'Bride of the Embalmer' is not completed"); + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` IN (19, 20) AND `SourceEntry` IN (349, 410, 431, 593, 779, 795, 813, 926, 961, 1191, 1462, 1463, 1464, 1714, 1789, 1790, 4041, 6581, 7645, 8508, 8732, 9483, 10850, 10919); +INSERT INTO `conditions` VALUES +(19, 0, 349, 0, 0, 9, 0, 348, 0, 0, 0, 0, 0, "", "Show quest 'Stranglethorn Fever' only if quest 'Stranglethorn Fever' is accepted but not completed"), +(20, 0, 349, 0, 0, 9, 0, 348, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Stranglethorn Fever' only if quest 'Stranglethorn Fever' is accepted but not completed"), + +(19, 0, 410, 0, 0, 9, 0, 409, 0, 0, 0, 0, 0, "", "Show quest 'The Dormant Shade' only if quest 'Proving Allegiance' is accepted but not completed"), +(20, 0, 410, 0, 0, 9, 0, 409, 0, 0, 0, 0, 0, "", "Show question mark for quest 'The Dormant Shade' only if quest 'Proving Allegiance' is accepted but not completed"), + +(19, 0, 431, 0, 0, 9, 0, 409, 0, 0, 0, 0, 0, "", "Show quest 'Candles of Beckoning' only if quest 'Proving Allegiance' is accepted but not completed"), +(20, 0, 431, 0, 0, 9, 0, 409, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Candles of Beckoning' only if quest 'Proving Allegiance' is accepted but not completed"), + +(19, 0, 593, 0, 0, 9, 0, 592, 0, 0, 0, 0, 0, "", "Show quest 'Filling the Soul Gem' only if quest 'Saving Yenniku' is accepted but not completed"), +(20, 0, 593, 0, 0, 9, 0, 592, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Filling the Soul Gem' only if quest 'Saving Yenniku' is accepted but not completed"), + +(19, 0, 779, 0, 0, 9, 0, 717, 0, 0, 0, 0, 0, "", "Show quest 'Seal of the Earth' only if quest 'Tremors of the Earth' is accepted but not completed"), +(20, 0, 779, 0, 0, 9, 0, 717, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Seal of the Earth' only if quest 'Tremors of the Earth' is accepted but not completed"), + +(19, 0, 795, 0, 0, 9, 0, 793, 0, 0, 0, 0, 0, "", "Show quest 'Seal of the Earth' only if quest 'Broken Alliances' is accepted but not completed"), +(20, 0, 795, 0, 0, 9, 0, 793, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Seal of the Earth' only if quest 'Broken Alliances' is accepted but not completed"), + +(19, 0, 813, 0, 0, 9, 0, 812, 0, 0, 0, 0, 0, "", "Show quest 'Finding the Antidote' only if quest 'Need for a Cure' is accepted"), +(20, 0, 813, 0, 0, 9, 0, 812, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Finding the Antidote' only if quest 'Need for a Cure' is accepted but not completed"), + +(19, 0, 926, 0, 0, 9, 0, 924, 0, 0, 0, 0, 0, "", "Show quest 'Flawed Power Stone' only if quest 'The Demon Seed' is accepted but not completed"), +(20, 0, 926, 0, 0, 9, 0, 924, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Flawed Power Stone' only if quest 'The Demon Seed' is accepted but not completed"), + +(19, 0, 961, 0, 0, 9, 0, 944, 0, 0, 0, 0, 0, "", "Show quest 'Onu is Meditating' only if quest 'The Demon Seed' is accepted but not completed OR"), +(20, 0, 961, 0, 0, 9, 0, 944, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Onu is Meditating' only if quest 'The Demon Seed' is accepted but not completed OR"), +(19, 0, 961, 0, 1, 9, 0, 949, 0, 0, 0, 0, 0, "", "Show quest 'Onu is Meditating' only if quest 'The Twilight Camp' is accepted but not completed"), +(20, 0, 961, 0, 1, 9, 0, 949, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Onu is Meditating' only if quest 'The Twilight Camp' is accepted but not completed"), + +(19, 0, 1191, 0, 0, 9, 0, 1190, 0, 0, 0, 0, 0, "", "Show quest 'Zamek's Distraction' only if quest 'Keeping Pace' is accepted but not completed"), +(20, 0, 1191, 0, 0, 9, 0, 1190, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Zamek's Distraction' only if quest 'Keeping Pace' is accepted but not completed"), + +(19, 0, 1462, 0, 0, 28, 0, 1520, 0, 0, 0, 0, 0, "", "Show quest 'Earth Sapta' only if quest 'Call of Earth' is completed"), +(20, 0, 1462, 0, 0, 28, 0, 1520, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Earth Sapta' only if quest 'Call of Earth' is completed"), + +(19, 0, 1463, 0, 0, 28, 0, 1517, 0, 0, 0, 0, 0, "", "Show quest 'Earth Sapta' only if quest 'Call of Earth' is ompleted"), +(20, 0, 1463, 0, 0, 28, 0, 1517, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Earth Sapta' only if quest 'Call of Earth' is completed"), + +(19, 0, 1464, 0, 0, 9, 0, 1526, 0, 0, 0, 0, 0, "", "Show quest 'Fire Sapta' only if quest 'Call of Fire' is accepted but not completed"), +(20, 0, 1464, 0, 0, 9, 0, 1526, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Fire Sapta' only if quest 'Call of Fire' is accepted but not completed"), + +(19, 0, 1714, 0, 0, 9, 0, 1712, 0, 0, 0, 0, 0, "", "Show quest 'Essence of the Exile' only if quest 'Cyclonian' is accepted but not completed"), +(20, 0, 1714, 0, 0, 9, 0, 1712, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Essence of the Exile' only if quest 'Cyclonian' is accepted but not completed"), + +(19, 0, 1789, 0, 0, 9, 0, 1783, 0, 0, 0, 0, 0, "", "Show quest 'The Symbol of Life' only if quest 'The Tome of Divinity' is accepted but not completed"), +(20, 0, 1789, 0, 0, 9, 0, 1783, 0, 0, 0, 0, 0, "", "Show question mark for quest 'The Symbol of Life' only if quest 'The Tome of Divinity' is accepted but not completed"), + +(19, 0, 1790, 0, 0, 9, 0, 1786, 0, 0, 0, 0, 0, "", "Show quest 'The Symbol of Life' only if quest 'The Tome of Divinity' is accepted but not completed"), +(20, 0, 1790, 0, 0, 9, 0, 1786, 0, 0, 0, 0, 0, "", "Show question mark for quest 'The Symbol of Life' only if quest 'The Tome of Divinity' is accepted but not completed"), + +(19, 0, 4041, 0, 0, 9, 0, 3909, 0, 0, 0, 0, 0, "", "Show quest 'The Videre Elixir' only if quest 'The Videre Elixir' is accepted but not completed"), +(20, 0, 4041, 0, 0, 9, 0, 3909, 0, 0, 0, 0, 0, "", "Show question mark for quest 'The Videre Elixir' only if quest 'The Videre Elixir' is accepted but not completed"), + +(19, 0, 6581, 0, 0, 9, 0, 6571, 0, 0, 0, 0, 0, "", "Show quest 'Warsong Saw Blades' only if quest 'Warsong Supplies' is accepted but not completed"), +(20, 0, 6581, 0, 0, 9, 0, 6571, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Warsong Saw Blades' only if quest 'Warsong Supplies' is accepted but not completed"), + +(19, 0, 7645, 0, 0, 9, 0, 7643, 0, 0, 0, 0, 0, "", "Show quest 'Manna-Enriched Horse Feed' only if quest 'Ancient Equine Spirit' is accepted but not completed"), +(20, 0, 7645, 0, 0, 9, 0, 7643, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Manna-Enriched Horse Feed' only if quest 'Ancient Equine Spirit' is accepted but not completed"), + +(19, 0, 8508, 0, 0, 9, 0, 8507, 0, 0, 0, 0, 0, "", "Show quest 'Field Duty Papers' only if quest 'Field Duty' is accepted but not completed"), +(20, 0, 8508, 0, 0, 9, 0, 8507, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Field Duty Papers' only if quest 'Field Duty' is accepted but not completed"), + +(19, 0, 8732, 0, 0, 9, 0, 8731, 0, 0, 0, 0, 0, "", "Show quest 'Field Duty Papers' only if quest 'Field Duty' is accepted but not completed"), +(20, 0, 8732, 0, 0, 9, 0, 8731, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Field Duty Papers' only if quest 'Field Duty' is accepted but not completed"), + +(19, 0, 9483, 0, 0, 9, 0, 9472, 0, 0, 0, 0, 0, "", "Show quest 'Life's Finer Pleasures' only if quest 'Arelion's Mistress' is accepted but not completed"), +(20, 0, 9483, 0, 0, 9, 0, 9472, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Life's Finer Pleasures' only if quest 'Arelion's Mistress' is accepted but not completed"), + +(19, 0, 10850, 0, 0, 9, 0, 10855, 0, 0, 0, 0, 0, "", "Show quest 'Nether Gas In a Fel Fire Engine' only if quest 'Fel Reavers, No Thanks!' is accepted but not completed"), +(20, 0, 10850, 0, 0, 9, 0, 10855, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Nether Gas In a Fel Fire Engine' only if quest 'Fel Reavers, No Thanks!' is accepted but not completed"), + +(19, 0, 10919, 0, 0, 9, 0, 10916, 0, 0, 0, 0, 0, "", "Show quest 'Fei Fei's Treat' only if quest 'Digging for Prayer Beads' is accepted but not completed"), +(20, 0, 10919, 0, 0, 9, 0, 10916, 0, 0, 0, 0, 0, "", "Show question mark for quest 'Fei Fei's Treat' only if quest 'Digging for Prayer Beads' is accepted but not completed"); diff --git a/sql/updates/world/3.3.5/2016_06_01_12_world.sql b/sql/updates/world/3.3.5/2016_06_01_12_world.sql new file mode 100644 index 00000000000..f6fb906bb70 --- /dev/null +++ b/sql/updates/world/3.3.5/2016_06_01_12_world.sql @@ -0,0 +1,4 @@ +-- +UPDATE `conditions` SET `SourceEntry`=49667 WHERE `SourceEntry`=48679 AND `SourceTypeOrReferenceId`=10 AND `SourceGroup`=10016; +UPDATE `smart_scripts` SET `link`=0 WHERE `entryorguid`=22137 AND `source_type`=0 AND `id`=7; +UPDATE `creature_text` SET `emote`=0 WHERE `entry`=19666 AND `groupid`=2 AND `id`=0; diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index 0d51f8ed060..85213a7f709 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -55,7 +55,7 @@ bool DBUpdaterUtil::CheckExecutable() } } - TC_LOG_FATAL("sql.updates", "Didn't find executeable mysql binary at \'%s\' or in path, correct the path in the *.conf (\"MySQLExecutable\").", + TC_LOG_FATAL("sql.updates", "Didn't find any executable MySQL binary at \'%s\' or in path, correct the path in the *.conf (\"MySQLExecutable\").", absolute(exe).generic_string().c_str()); return false; @@ -178,7 +178,7 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool) // Path of temp file static Path const temp("create_table.sql"); - // Create temporary query to use external mysql cli + // Create temporary query to use external MySQL CLi std::ofstream file(temp.generic_string()); if (!file.is_open()) { @@ -197,7 +197,7 @@ bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool) } catch (UpdateException&) { - TC_LOG_FATAL("sql.updates", "Failed to create database %s! Has the user `CREATE` priviliges?", pool.GetConnectionInfo()->database.c_str()); + TC_LOG_FATAL("sql.updates", "Failed to create database %s! Does the user (named in *.conf) have `CREATE` privileges on the MySQL server?", pool.GetConnectionInfo()->database.c_str()); boost::filesystem::remove(temp); return false; } @@ -280,7 +280,7 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool) { case LOCATION_REPOSITORY: { - TC_LOG_ERROR("sql.updates", ">> Base file \"%s\" is missing, try to clone the source again.", + TC_LOG_ERROR("sql.updates", ">> Base file \"%s\" is missing. Try fixing it by cloning the source again.", base.generic_string().c_str()); break; @@ -288,7 +288,7 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool) case LOCATION_DOWNLOAD: { TC_LOG_ERROR("sql.updates", ">> File \"%s\" is missing, download it from \"https://github.com/TrinityCore/TrinityCore/releases\"" \ - " and place it in your server directory.", base.filename().generic_string().c_str()); + " and place it in your worldserver directory.", base.filename().generic_string().c_str()); break; } } @@ -355,7 +355,7 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos if (!std::isdigit(port_or_socket[0])) { - // We can't check here if host == "." because is named localhost if socket option is enabled + // We can't check if host == "." here, because it is named localhost if socket option is enabled args.push_back("-P0"); args.push_back("--protocol=SOCKET"); args.push_back("-S" + port_or_socket); @@ -383,7 +383,7 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos if (ret != EXIT_SUCCESS) { TC_LOG_FATAL("sql.updates", "Applying of file \'%s\' to database \'%s\' failed!" \ - " If you are an user pull the latest revision from the repository. If you are a developer fix your sql query.", + " If you are a user, pull the latest revision from the repository. If you are a developer, fix your sql query.", path.generic_string().c_str(), pool.GetConnectionInfo()->database.c_str()); throw UpdateException("update failed"); diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp index 12bfb86251b..68cb268ac6c 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.cpp +++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp @@ -19,6 +19,51 @@ #include "SpellAuras.h" #include "SpellAuraEffects.h" +static const uint8 NUM_TALENT_TREES = 3; +static const uint8 NUM_SPEC_ICONICS = 3; + +enum Specs +{ + SPEC_WARRIOR_ARMS = 0, + SPEC_WARRIOR_FURY = 1, + SPEC_WARRIOR_PROTECTION = 2, + + SPEC_PALADIN_HOLY = 0, + SPEC_PALADIN_PROTECTION = 1, + SPEC_PALADIN_RETRIBUTION = 2, + + SPEC_HUNTER_BEAST_MASTERY = 0, + SPEC_HUNTER_MARKSMANSHIP = 1, + SPEC_HUNTER_SURVIVAL = 2, + + SPEC_ROGUE_ASSASSINATION = 0, + SPEC_ROGUE_COMBAT = 1, + SPEC_ROGUE_SUBLETY = 2, + + SPEC_PRIEST_DISCIPLINE = 0, + SPEC_PRIEST_HOLY = 1, + SPEC_PRIEST_SHADOW = 2, + + SPEC_DEATH_KNIGHT_BLOOD = 0, + SPEC_DEATH_KNIGHT_FROST = 1, + SPEC_DEATH_KNIGHT_UNHOLY = 2, + + SPEC_SHAMAN_ELEMENTAL = 0, + SPEC_SHAMAN_ENHANCEMENT = 1, + SPEC_SHAMAN_RESTORATION = 2, + + SPEC_MAGE_ARCANE = 0, + SPEC_MAGE_FIRE = 1, + SPEC_MAGE_FROST = 2, + + SPEC_WARLOCK_AFFLICTION = 0, + SPEC_WARLOCK_DEMONOLOGY = 1, + SPEC_WARLOCK_DESTRUCTION = 2, + + SPEC_DRUID_BALANCE = 0, + SPEC_DRUID_FERAL = 1, + SPEC_DRUID_RESTORATION = 2 +}; enum Spells { /* Generic */ @@ -27,29 +72,449 @@ enum Spells SPELL_THROW = 2764, SPELL_SHOOT_WAND = 5019, - /* Paladin */ - PASSIVE_ILLUMINATION = 20215, + /* Warrior - Generic */ + SPELL_BATTLE_STANCE = 2457, + SPELL_BERSERKER_STANCE = 2458, + SPELL_DEFENSIVE_STANCE = 71, + SPELL_CHARGE = 11578, + SPELL_INTERCEPT = 20252, + SPELL_ENRAGED_REGEN = 55694, + SPELL_INTIMIDATING_SHOUT= 5246, + SPELL_PUMMEL = 6552, + SPELL_SHIELD_BASH = 72, + SPELL_BLOODRAGE = 2687, + + /* Warrior - Arms */ + SPELL_SWEEPING_STRIKES = 12328, + SPELL_MORTAL_STRIKE = 12294, + SPELL_BLADESTORM = 46924, + SPELL_REND = 47465, + SPELL_RETALIATION = 20230, + SPELL_SHATTERING_THROW = 64382, + SPELL_THUNDER_CLAP = 47502, + + /* Warrior - Fury */ + SPELL_DEATH_WISH = 12292, + SPELL_BLOODTHIRST = 23881, + PASSIVE_TITANS_GRIP = 46917, + SPELL_DEMO_SHOUT = 47437, + SPELL_EXECUTE = 47471, + SPELL_HEROIC_FURY = 60970, + SPELL_RECKLESSNESS = 1719, + SPELL_PIERCING_HOWL = 12323, + + /* Warrior - Protection */ + SPELL_VIGILANCE = 50720, + SPELL_DEVASTATE = 20243, + SPELL_SHOCKWAVE = 46968, + SPELL_CONCUSSION_BLOW = 12809, + SPELL_DISARM = 676, + SPELL_LAST_STAND = 12975, + SPELL_SHIELD_BLOCK = 2565, + SPELL_SHIELD_SLAM = 47488, + SPELL_SHIELD_WALL = 871, + SPELL_SPELL_REFLECTION = 23920, + + /* Paladin - Generic */ + SPELL_AURA_MASTERY = 31821, + SPELL_LAY_ON_HANDS = 48788, + SPELL_BLESSING_OF_MIGHT = 48932, + SPELL_AVENGING_WRATH = 31884, + SPELL_DIVINE_PROTECTION = 498, + SPELL_DIVINE_SHIELD = 642, + SPELL_HAMMER_OF_JUSTICE = 10308, + SPELL_HAND_OF_FREEDOM = 1044, + SPELL_HAND_OF_PROTECTION = 10278, + SPELL_HAND_OF_SACRIFICE = 6940, + + /* Paladin - Holy*/ + PASSIVE_ILLUMINATION = 20215, + SPELL_HOLY_SHOCK = 20473, + SPELL_BEACON_OF_LIGHT = 53563, + SPELL_CONSECRATION = 48819, + SPELL_FLASH_OF_LIGHT = 48785, + SPELL_HOLY_LIGHT = 48782, + SPELL_DIVINE_FAVOR = 20216, + SPELL_DIVINE_ILLUMINATION = 31842, + + /* Paladin - Protection */ + SPELL_BLESS_OF_SANC = 20911, + SPELL_HOLY_SHIELD = 20925, + SPELL_AVENGERS_SHIELD = 48827, + SPELL_DIVINE_SACRIFICE = 64205, + SPELL_HAMMER_OF_RIGHTEOUS = 53595, + SPELL_RIGHTEOUS_FURY = 25780, + SPELL_SHIELD_OF_RIGHTEOUS = 61411, + + /* Paladin - Retribution */ + SPELL_SEAL_OF_COMMAND = 20375, + SPELL_CRUSADER_STRIKE = 35395, + SPELL_DIVINE_STORM = 53385, + SPELL_JUDGEMENT = 20271, + SPELL_HAMMER_OF_WRATH = 48806, + + /* Hunter - Generic */ + SPELL_DETERRENCE = 19263, + SPELL_EXPLOSIVE_TRAP = 49067, + SPELL_FREEZING_ARROW = 60192, + SPELL_RAPID_FIRE = 3045, + SPELL_KILL_SHOT = 61006, + SPELL_MULTI_SHOT = 49048, + SPELL_VIPER_STING = 3034, + + /* Hunter - Beast Mastery */ + SPELL_BESTIAL_WRATH = 19574, + PASSIVE_BEAST_WITHIN = 34692, + PASSIVE_BEAST_MASTERY = 53270, + + /* Hunter - Marksmanship */ + SPELL_AIMED_SHOT = 19434, + PASSIVE_TRUESHOT_AURA = 19506, + SPELL_CHIMERA_SHOT = 53209, + SPELL_ARCANE_SHOT = 49045, + SPELL_STEADY_SHOT = 49052, + SPELL_READINESS = 23989, + SPELL_SILENCING_SHOT = 34490, - /* Priest */ - SPELL_SOUL_WARDING = 63574, - SPELL_SPIRIT_REDEMPTION = 20711, - SPELL_SHADOWFORM = 15473, + /* Hunter - Survival */ + PASSIVE_LOCK_AND_LOAD = 56344, + SPELL_WYVERN_STING = 19386, + SPELL_EXPLOSIVE_SHOT = 53301, + SPELL_BLACK_ARROW = 3674, - /* Shaman */ - SPELL_TIDAL_FORCE = 582, - SPELL_MANA_TIDE_TOTEM = 590, - SPELL_SHA_NATURE_SWIFT = 591, + /* Rogue - Generic */ + SPELL_DISMANTLE = 51722, + SPELL_EVASION = 26669, + SPELL_KICK = 1766, + SPELL_VANISH = 26889, + SPELL_BLIND = 2094, + SPELL_CLOAK_OF_SHADOWS = 31224, + + /* Rogue - Assassination */ + SPELL_COLD_BLOOD = 14177, + SPELL_MUTILATE = 1329, + SPELL_HUNGER_FOR_BLOOD = 51662, + SPELL_ENVENOM = 57993, + + /* Rogue - Combat */ + SPELL_SINISTER_STRIKE = 48637, + SPELL_BLADE_FLURRY = 13877, + SPELL_ADRENALINE_RUSH = 13750, + SPELL_KILLING_SPREE = 51690, + SPELL_EVISCERATE = 48668, + + /* Rogue - Sublety */ + SPELL_HEMORRHAGE = 16511, + SPELL_PREMEDITATION = 14183, + SPELL_SHADOW_DANCE = 51713, + SPELL_PREPARATION = 14185, + SPELL_SHADOWSTEP = 36554, + + /* Priest - Generic */ + SPELL_FEAR_WARD = 6346, + SPELL_POWER_WORD_FORT = 48161, + SPELL_DIVINE_SPIRIT = 48073, + SPELL_SHADOW_PROTECTION = 48169, + SPELL_DIVINE_HYMN = 64843, + SPELL_HYMN_OF_HOPE = 64901, + SPELL_SHADOW_WORD_DEATH = 48158, + SPELL_PSYCHIC_SCREAM = 10890, + + /* Priest - Discipline */ + PASSIVE_SOUL_WARDING = 63574, + SPELL_POWER_INFUSION = 10060, + SPELL_PENANCE = 47540, + SPELL_PAIN_SUPPRESSION = 33206, + SPELL_INNER_FOCUS = 14751, + SPELL_POWER_WORD_SHIELD = 48066, + + /* Priest - Holy */ + PASSIVE_SPIRIT_REDEMPTION = 20711, + SPELL_DESPERATE_PRAYER = 19236, + SPELL_GUARDIAN_SPIRIT = 47788, + SPELL_FLASH_HEAL = 48071, + SPELL_RENEW = 48068, + + /* Priest - Shadow */ + SPELL_VAMPIRIC_EMBRACE = 15286, + SPELL_SHADOWFORM = 15473, + SPELL_VAMPIRIC_TOUCH = 34914, + SPELL_MIND_FLAY = 15407, + SPELL_MIND_BLAST = 48127, + SPELL_SHADOW_WORD_PAIN = 48125, + SPELL_DEVOURING_PLAGUE = 48300, + SPELL_DISPERSION = 47585, + + /* Death Knight - Generic */ + SPELL_DEATH_GRIP = 49576, + SPELL_STRANGULATE = 47476, + SPELL_EMPOWER_RUNE_WEAP = 47568, + SPELL_ICEBORN_FORTITUDE = 48792, + SPELL_ANTI_MAGIC_SHELL = 48707, + SPELL_DEATH_COIL_DK = 49895, + SPELL_MIND_FREEZE = 47528, + SPELL_ICY_TOUCH = 49909, + AURA_FROST_FEVER = 55095, + SPELL_PLAGUE_STRIKE = 49921, + AURA_BLOOD_PLAGUE = 55078, + SPELL_PESTILENCE = 50842, + + /* Death Knight - Blood */ + SPELL_RUNE_TAP = 48982, + SPELL_HYSTERIA = 49016, + SPELL_HEART_STRIKE = 55050, + SPELL_DEATH_STRIKE = 49924, + SPELL_BLOOD_STRIKE = 49930, + SPELL_MARK_OF_BLOOD = 49005, + SPELL_VAMPIRIC_BLOOD = 55233, + + /* Death Knight - Frost */ + PASSIVE_ICY_TALONS = 50887, + SPELL_FROST_STRIKE = 49143, + SPELL_HOWLING_BLAST = 49184, + SPELL_UNBREAKABLE_ARMOR = 51271, + SPELL_OBLITERATE = 51425, + SPELL_DEATHCHILL = 49796, + + /* Death Knight - Unholy */ + PASSIVE_UNHOLY_BLIGHT = 49194, + PASSIVE_MASTER_OF_GHOUL = 52143, + SPELL_SCOURGE_STRIKE = 55090, + SPELL_DEATH_AND_DECAY = 49938, + SPELL_ANTI_MAGIC_ZONE = 51052, + SPELL_SUMMON_GARGOYLE = 49206, + + /* Shaman - Generic */ + SPELL_HEROISM = 32182, + SPELL_BLOODLUST = 2825, + SPELL_GROUNDING_TOTEM = 8177, + + /* Shaman - Elemental*/ + PASSIVE_ELEMENTAL_FOCUS = 16164, + SPELL_TOTEM_OF_WRATH = 30706, + SPELL_THUNDERSTORM = 51490, + SPELL_LIGHTNING_BOLT = 49238, + SPELL_EARTH_SHOCK = 49231, + SPELL_FLAME_SHOCK = 49233, + SPELL_LAVA_BURST = 60043, + SPELL_CHAIN_LIGHTNING = 49271, + SPELL_ELEMENTAL_MASTERY = 16166, + + /* Shaman - Enhancement */ + PASSIVE_SPIRIT_WEAPONS = 16268, + SPELL_LAVA_LASH = 60103, + SPELL_FERAL_SPIRIT = 51533, + AURA_MAELSTROM_WEAPON = 53817, SPELL_STORMSTRIKE = 17364, + SPELL_SHAMANISTIC_RAGE = 30823, + + /* Shaman - Restoration*/ + SPELL_SHA_NATURE_SWIFT = 591, + SPELL_MANA_TIDE_TOTEM = 590, + SPELL_EARTH_SHIELD = 49284, + SPELL_RIPTIDE = 61295, + SPELL_HEALING_WAVE = 49273, + SPELL_LESSER_HEAL_WAVE = 49276, + SPELL_TIDAL_FORCE = 55198, + + /* Mage - Generic */ + SPELL_DAMPEN_MAGIC = 43015, + SPELL_EVOCATION = 12051, + SPELL_MANA_SHIELD = 43020, + SPELL_MIRROR_IMAGE = 55342, + SPELL_SPELLSTEAL = 30449, + SPELL_COUNTERSPELL = 2139, + SPELL_ICE_BLOCK = 45438, + + /* Mage - Arcane */ + SPELL_FOCUS_MAGIC = 54646, + SPELL_ARCANE_POWER = 12042, + SPELL_ARCANE_BARRAGE = 44425, + SPELL_ARCANE_BLAST = 42897, + AURA_ARCANE_BLAST = 36032, + SPELL_ARCANE_MISSILES = 42846, + SPELL_PRESENCE_OF_MIND = 12043, + + /* Mage - Fire */ + SPELL_PYROBLAST = 11366, + SPELL_COMBUSTION = 11129, + SPELL_LIVING_BOMB = 44457, + SPELL_FIREBALL = 42833, + SPELL_FIRE_BLAST = 42873, + SPELL_DRAGONS_BREATH = 31661, + SPELL_BLAST_WAVE = 11113, + + /* Mage - Frost */ + SPELL_ICY_VEINS = 12472, + SPELL_ICE_BARRIER = 11426, + SPELL_DEEP_FREEZE = 44572, + SPELL_FROST_NOVA = 42917, + SPELL_FROSTBOLT = 42842, + SPELL_COLD_SNAP = 11958, + SPELL_ICE_LANCE = 42914, + + /* Warlock - Generic */ + SPELL_FEAR = 6215, + SPELL_HOWL_OF_TERROR = 17928, + SPELL_CORRUPTION = 47813, + SPELL_DEATH_COIL_W = 47860, + SPELL_SHADOW_BOLT = 47809, + SPELL_INCINERATE = 47838, + SPELL_IMMOLATE = 47811, + SPELL_SEED_OF_CORRUPTION = 47836, + + /* Warlock - Affliction */ + PASSIVE_SIPHON_LIFE = 63108, + SPELL_UNSTABLE_AFFLICTION = 30108, + SPELL_HAUNT = 48181, + SPELL_CURSE_OF_AGONY = 47864, + SPELL_DRAIN_SOUL = 47855, - /* Druid */ - SPELL_MOONKIN_FORM = 24858, - SPELL_SWIFTMEND = 18562, - SPELL_DRU_NATURE_SWIFT = 17116, - SPELL_TREE_OF_LIFE = 33891 + /* Warlock - Demonology */ + SPELL_SOUL_LINK = 19028, + SPELL_DEMONIC_EMPOWERMENT = 47193, + SPELL_METAMORPHOSIS = 59672, + SPELL_IMMOLATION_AURA = 50589, + SPELL_DEMON_CHARGE = 54785, + AURA_DECIMATION = 63167, + AURA_MOLTEN_CORE = 71165, + SPELL_SOUL_FIRE = 47825, + + /* Warlock - Destruction */ + SPELL_SHADOWBURN = 17877, + SPELL_CONFLAGRATE = 17962, + SPELL_CHAOS_BOLT = 50796, + SPELL_SHADOWFURY = 47847, + + /* Druid - Generic */ + SPELL_BARKSKIN = 22812, + SPELL_INNERVATE = 29166, + + /* Druid - Balance */ + SPELL_INSECT_SWARM = 5570, + SPELL_MOONKIN_FORM = 24858, + SPELL_STARFALL = 48505, + SPELL_TYPHOON = 61384, + AURA_ECLIPSE_LUNAR = 48518, + SPELL_MOONFIRE = 48463, + SPELL_STARFIRE = 48465, + SPELL_WRATH = 48461, + + /* Druid - Feral */ + SPELL_CAT_FORM = 768, + SPELL_SURVIVAL_INSTINCTS = 61336, + SPELL_MANGLE = 33917, + SPELL_BERSERK = 50334, + SPELL_MANGLE_CAT = 48566, + SPELL_FERAL_CHARGE_CAT = 49376, + SPELL_RAKE = 48574, + SPELL_RIP = 49800, + SPELL_SAVAGE_ROAR = 52610, + SPELL_TIGER_FURY = 50213, + SPELL_CLAW = 48570, + SPELL_DASH = 33357, + SPELL_MAIM = 49802, + + /* Druid - Restoration */ + SPELL_SWIFTMEND = 18562, + SPELL_TREE_OF_LIFE = 33891, + SPELL_WILD_GROWTH = 48438, + SPELL_NATURE_SWIFTNESS = 17116, + SPELL_TRANQUILITY = 48447, + SPELL_NOURISH = 50464, + SPELL_HEALING_TOUCH = 48378, + SPELL_REJUVENATION = 48441, + SPELL_REGROWTH = 48443, + SPELL_LIFEBLOOM = 48451 }; +// As it turns out, finding out "how many points does the player have in spec X" is actually really expensive to do frequently +// So instead, we just check for a handful of spells that, realistically, no spec is gonna go without (and "has spell" is cheap!) +// Can players deliberately trick this check? Yes. +// Is it worth doing? No. +// Close enough. +static const uint32 SPEC_ICONICS[MAX_CLASSES][NUM_TALENT_TREES][NUM_SPEC_ICONICS] = { + { // CLASS_NONE + {0,0,0}, + {0,0,0}, + {0,0,0} + }, + { // CLASS_WARRIOR + {SPELL_BLADESTORM, SPELL_MORTAL_STRIKE, SPELL_SWEEPING_STRIKES}, // Arms + {PASSIVE_TITANS_GRIP, SPELL_BLOODTHIRST, SPELL_DEATH_WISH}, // Fury + {SPELL_SHOCKWAVE, SPELL_DEVASTATE, SPELL_VIGILANCE} // Protection + }, + { // CLASS_PALADIN + {SPELL_BEACON_OF_LIGHT, SPELL_HOLY_SHOCK, PASSIVE_ILLUMINATION}, // Holy + {SPELL_HAMMER_OF_RIGHTEOUS, SPELL_HOLY_SHIELD, SPELL_BLESS_OF_SANC}, // Protection + {SPELL_DIVINE_STORM, SPELL_CRUSADER_STRIKE, SPELL_SEAL_OF_COMMAND} // Retribution + }, + { // CLASS_HUNTER + {PASSIVE_BEAST_MASTERY, PASSIVE_BEAST_WITHIN, SPELL_BESTIAL_WRATH}, // Beast Mastery + {SPELL_CHIMERA_SHOT, PASSIVE_TRUESHOT_AURA, SPELL_AIMED_SHOT}, // Marksmanship + {SPELL_EXPLOSIVE_SHOT, SPELL_WYVERN_STING, PASSIVE_LOCK_AND_LOAD} // Survival + }, + { // CLASS_ROGUE + {SPELL_HUNGER_FOR_BLOOD, SPELL_MUTILATE, SPELL_COLD_BLOOD}, // Assassination + {SPELL_KILLING_SPREE, SPELL_ADRENALINE_RUSH, SPELL_BLADE_FLURRY}, // Combat + {SPELL_SHADOW_DANCE, SPELL_PREMEDITATION, SPELL_HEMORRHAGE} // Sublety + }, + { // CLASS_PRIEST + {SPELL_PENANCE, SPELL_POWER_INFUSION, PASSIVE_SOUL_WARDING}, // Discipline + {SPELL_GUARDIAN_SPIRIT, PASSIVE_SPIRIT_REDEMPTION, SPELL_DESPERATE_PRAYER}, // Holy + {SPELL_VAMPIRIC_TOUCH, SPELL_SHADOWFORM, SPELL_VAMPIRIC_EMBRACE} // Shadow + }, + { // CLASS_DEATH_KNIGHT + {SPELL_HEART_STRIKE, SPELL_HYSTERIA, SPELL_RUNE_TAP}, // Blood + {SPELL_HOWLING_BLAST, SPELL_FROST_STRIKE, PASSIVE_ICY_TALONS}, // Frost + {SPELL_SCOURGE_STRIKE, PASSIVE_MASTER_OF_GHOUL, PASSIVE_UNHOLY_BLIGHT} // Unholy + }, + { // CLASS_SHAMAN + {SPELL_THUNDERSTORM, SPELL_TOTEM_OF_WRATH, PASSIVE_ELEMENTAL_FOCUS}, // Elemental + {SPELL_FERAL_SPIRIT, SPELL_LAVA_LASH, PASSIVE_SPIRIT_WEAPONS}, // Enhancement + {SPELL_RIPTIDE, SPELL_MANA_TIDE_TOTEM, SPELL_SHA_NATURE_SWIFT} // Restoration + }, + { // CLASS_MAGE + {SPELL_ARCANE_BARRAGE, SPELL_ARCANE_POWER, SPELL_FOCUS_MAGIC}, // Arcane + {SPELL_LIVING_BOMB, SPELL_COMBUSTION, SPELL_PYROBLAST}, // Fire + {SPELL_DEEP_FREEZE, SPELL_ICE_BARRIER, SPELL_ICY_VEINS} // Frost + }, + { // CLASS_WARLOCK + {SPELL_HAUNT, SPELL_UNSTABLE_AFFLICTION, PASSIVE_SIPHON_LIFE}, // Affliction + {SPELL_METAMORPHOSIS, SPELL_DEMONIC_EMPOWERMENT, SPELL_SOUL_LINK}, // Demonology + {SPELL_CHAOS_BOLT, SPELL_CONFLAGRATE, SPELL_SHADOWBURN} // Destruction + }, + { // CLASS_UNK + {0,0,0}, + {0,0,0}, + {0,0,0} + }, + { // CLASS_DRUID + {SPELL_STARFALL, SPELL_MOONKIN_FORM, SPELL_INSECT_SWARM}, // Balance + {SPELL_BERSERK, SPELL_MANGLE, SPELL_SURVIVAL_INSTINCTS}, // Feral + {SPELL_WILD_GROWTH, SPELL_TREE_OF_LIFE, SPELL_SWIFTMEND} // Restoration + } +}; + +uint8 PlayerAI::GetPlayerSpec(Player const* who) +{ + if (!who) + return 0; + + uint8 wClass = who->getClass(); + for (uint8 tier = 0; tier < NUM_SPEC_ICONICS; ++tier) + for (uint8 tree = 0; tree < NUM_TALENT_TREES; ++tree) + if (SPEC_ICONICS[wClass][tree][tier] && who->HasSpell(SPEC_ICONICS[wClass][tree][tier])) + return tree; + + return 0; +} + bool PlayerAI::IsPlayerHealer(Player const* who) { + if (!who) + return false; + switch (who->getClass()) { case CLASS_WARRIOR: @@ -61,18 +526,21 @@ bool PlayerAI::IsPlayerHealer(Player const* who) default: return false; case CLASS_PALADIN: - return who->HasSpell(PASSIVE_ILLUMINATION); + return (PlayerAI::GetPlayerSpec(who) == SPEC_PALADIN_HOLY); case CLASS_PRIEST: - return who->HasSpell(SPELL_SOUL_WARDING) || who->HasSpell(SPELL_SPIRIT_REDEMPTION); + return (PlayerAI::GetPlayerSpec(who) != SPEC_PRIEST_SHADOW); case CLASS_SHAMAN: - return who->HasSpell(SPELL_MANA_TIDE_TOTEM) || who->HasSpell(SPELL_SHA_NATURE_SWIFT) || who->HasSpell(SPELL_TIDAL_FORCE); + return (PlayerAI::GetPlayerSpec(who) == SPEC_SHAMAN_RESTORATION); case CLASS_DRUID: - return who->HasSpell(SPELL_SWIFTMEND) || who->HasSpell(SPELL_DRU_NATURE_SWIFT) || who->HasSpell(SPELL_TREE_OF_LIFE); + return (PlayerAI::GetPlayerSpec(who) == SPEC_DRUID_RESTORATION); } } bool PlayerAI::IsPlayerRangedAttacker(Player const* who) { + if (!who) + return false; + switch (who->getClass()) { case CLASS_WARRIOR: @@ -94,12 +562,106 @@ bool PlayerAI::IsPlayerRangedAttacker(Player const* who) return false; } case CLASS_PRIEST: - return who->HasSpell(SPELL_SHADOWFORM); + return (PlayerAI::GetPlayerSpec(who) == SPEC_PRIEST_SHADOW); case CLASS_SHAMAN: - return !who->HasSpell(SPELL_STORMSTRIKE); + return (PlayerAI::GetPlayerSpec(who) == SPEC_SHAMAN_ELEMENTAL); case CLASS_DRUID: - return who->HasSpell(SPELL_MOONKIN_FORM); + return (PlayerAI::GetPlayerSpec(who) == SPEC_DRUID_BALANCE); + } +} + +PlayerAI::TargetedSpell PlayerAI::VerifySpellCast(uint32 spellId, Unit* target) +{ + // Find highest spell rank that we know + uint32 knownRank, nextRank; + if (me->HasSpell(spellId)) + { + // this will save us some lookups if the player has the highest rank (expected case) + knownRank = spellId; + nextRank = sSpellMgr->GetNextSpellInChain(spellId); + } + else + { + knownRank = 0; + nextRank = sSpellMgr->GetFirstSpellInChain(spellId); + } + + while (nextRank && me->HasSpell(nextRank)) + { + knownRank = nextRank; + nextRank = sSpellMgr->GetNextSpellInChain(knownRank); + } + + if (!knownRank) + return {}; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(knownRank); + if (!spellInfo) + return {}; + + if (me->GetSpellHistory()->HasGlobalCooldown(spellInfo)) + return {}; + + Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE); + if (spell->CanAutoCast(target)) + return{ spell, target }; + + delete spell; + return {}; +} + +PlayerAI::TargetedSpell PlayerAI::VerifySpellCast(uint32 spellId, SpellTarget target) +{ + Unit* pTarget = nullptr; + switch (target) + { + case TARGET_NONE: + break; + case TARGET_VICTIM: + pTarget = me->GetVictim(); + if (!pTarget) + return {}; + break; + case TARGET_CHARMER: + pTarget = me->GetCharmer(); + if (!pTarget) + return {}; + break; + case TARGET_SELF: + pTarget = me; + break; + } + + return VerifySpellCast(spellId, pTarget); +} + +PlayerAI::TargetedSpell PlayerAI::SelectSpellCast(PossibleSpellVector& spells) +{ + uint32 totalWeights = 0; + for (PossibleSpell const& wSpell : spells) + totalWeights += wSpell.second; + + TargetedSpell selected; + uint32 randNum = urand(0, totalWeights - 1); + for (PossibleSpell const& wSpell : spells) + { + if (selected) + { + delete wSpell.first.first; + continue; + } + + if (randNum < wSpell.second) + selected = wSpell.first; + else + { + randNum -= wSpell.second; + delete wSpell.first.first; + } } + + spells.clear(); + return selected; } void PlayerAI::DoRangedAttackIfReady() @@ -150,6 +712,29 @@ void PlayerAI::DoAutoAttackIfReady() DoMeleeAttackIfReady(); } +void PlayerAI::CancelAllShapeshifts() +{ + std::list<AuraEffect*> const& shapeshiftAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_SHAPESHIFT); + std::set<Aura*> removableShapeshifts; + for (AuraEffect* auraEff : shapeshiftAuras) + { + Aura* aura = auraEff->GetBase(); + if (!aura) + continue; + SpellInfo const* auraInfo = aura->GetSpellInfo(); + if (!auraInfo) + continue; + if (auraInfo->HasAttribute(SPELL_ATTR0_CANT_CANCEL)) + continue; + if (!auraInfo->IsPositive() || auraInfo->IsPassive()) + continue; + removableShapeshifts.insert(aura); + } + + for (Aura* aura : removableShapeshifts) + me->RemoveOwnedAura(aura, AURA_REMOVE_BY_CANCEL); +} + struct UncontrolledTargetSelectPredicate : public std::unary_function<Unit*, bool> { bool operator()(Unit const* target) const @@ -164,7 +749,516 @@ Unit* SimpleCharmedPlayerAI::SelectAttackTarget() const return nullptr; } -void SimpleCharmedPlayerAI::UpdateAI(const uint32 /*diff*/) +PlayerAI::TargetedSpell SimpleCharmedPlayerAI::SelectAppropriateCastForSpec() +{ + PossibleSpellVector spells; + + switch (me->getClass()) + { + case CLASS_WARRIOR: + if (!me->IsWithinMeleeRange(me->GetVictim())) + { + VerifyAndPushSpellCast(spells, SPELL_CHARGE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_INTERCEPT, TARGET_VICTIM, 10); + } + VerifyAndPushSpellCast(spells, SPELL_ENRAGED_REGEN, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_INTIMIDATING_SHOUT, TARGET_VICTIM, 4); + if (me->GetVictim() && me->GetVictim()->HasUnitState(UNIT_STATE_CASTING)) + { + VerifyAndPushSpellCast(spells, SPELL_PUMMEL, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_BASH, TARGET_VICTIM, 15); + } + VerifyAndPushSpellCast(spells, SPELL_BLOODRAGE, TARGET_NONE, 5); + switch (GetSpec()) + { + case SPEC_WARRIOR_PROTECTION: + VerifyAndPushSpellCast(spells, SPELL_SHOCKWAVE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_CONCUSSION_BLOW, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_DISARM, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LAST_STAND, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_BLOCK, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_SLAM, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_WALL, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SPELL_REFLECTION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_DEVASTATE, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_REND, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_THUNDER_CLAP, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_DEMO_SHOUT, TARGET_VICTIM, 1); + break; + case SPEC_WARRIOR_ARMS: + VerifyAndPushSpellCast(spells, SPELL_SWEEPING_STRIKES, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_MORTAL_STRIKE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_BLADESTORM, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_REND, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_RETALIATION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_SHATTERING_THROW, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_SWEEPING_STRIKES, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_THUNDER_CLAP, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_EXECUTE, TARGET_VICTIM, 15); + break; + case SPEC_WARRIOR_FURY: + VerifyAndPushSpellCast(spells, SPELL_DEATH_WISH, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_BLOODTHIRST, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_DEMO_SHOUT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_EXECUTE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_HEROIC_FURY, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_RECKLESSNESS, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_PIERCING_HOWL, TARGET_VICTIM, 2); + break; + } + break; + case CLASS_PALADIN: + VerifyAndPushSpellCast(spells, SPELL_AURA_MASTERY, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_LAY_ON_HANDS, TARGET_CHARMER, 8); + VerifyAndPushSpellCast(spells, SPELL_BLESSING_OF_MIGHT, TARGET_CHARMER, 8); + VerifyAndPushSpellCast(spells, SPELL_AVENGING_WRATH, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_PROTECTION, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SHIELD, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_JUSTICE, TARGET_VICTIM, 6); + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_FREEDOM, TARGET_SELF, 3); + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_PROTECTION, TARGET_SELF, 1); + if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID())) + { + if (creatureCharmer->IsDungeonBoss() || creatureCharmer->isWorldBoss()) + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_SACRIFICE, creatureCharmer, 10); + else + VerifyAndPushSpellCast(spells, SPELL_HAND_OF_PROTECTION, creatureCharmer, 3); + } + + switch (GetSpec()) + { + case SPEC_PALADIN_PROTECTION: + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_RIGHTEOUS, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SACRIFICE, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_SHIELD_OF_RIGHTEOUS, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_JUDGEMENT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CONSECRATION, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHIELD, TARGET_NONE, 1); + break; + case SPEC_PALADIN_HOLY: + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHOCK, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_HOLY_SHOCK, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_FLASH_OF_LIGHT, TARGET_CHARMER, 4); + VerifyAndPushSpellCast(spells, SPELL_HOLY_LIGHT, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_FAVOR, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_ILLUMINATION, TARGET_NONE, 3); + break; + case SPEC_PALADIN_RETRIBUTION: + VerifyAndPushSpellCast(spells, SPELL_CRUSADER_STRIKE, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_STORM, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_JUDGEMENT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_WRATH, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_RIGHTEOUS_FURY, TARGET_NONE, 2); + break; + } + break; + case CLASS_HUNTER: + VerifyAndPushSpellCast(spells, SPELL_DETERRENCE, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_EXPLOSIVE_TRAP, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_FREEZING_ARROW, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_RAPID_FIRE, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_KILL_SHOT, TARGET_VICTIM, 10); + if (me->GetVictim() && me->GetVictim()->getPowerType() == POWER_MANA && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_VIPER_STING, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_VIPER_STING, TARGET_VICTIM, 5); + + switch (GetSpec()) + { + case SPEC_HUNTER_BEAST_MASTERY: + VerifyAndPushSpellCast(spells, SPELL_AIMED_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_MULTI_SHOT, TARGET_VICTIM, 2); + break; + case SPEC_HUNTER_MARKSMANSHIP: + VerifyAndPushSpellCast(spells, SPELL_AIMED_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CHIMERA_SHOT, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_READINESS, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_SILENCING_SHOT, TARGET_VICTIM, 5); + break; + case SPEC_HUNTER_SURVIVAL: + VerifyAndPushSpellCast(spells, SPELL_EXPLOSIVE_SHOT, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_BLACK_ARROW, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_MULTI_SHOT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_STEADY_SHOT, TARGET_VICTIM, 1); + break; + } + break; + case CLASS_ROGUE: + { + VerifyAndPushSpellCast(spells, SPELL_DISMANTLE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_EVASION, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_VANISH, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_BLIND, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_CLOAK_OF_SHADOWS, TARGET_NONE, 2); + + uint32 builder, finisher; + switch (GetSpec()) + { + case SPEC_ROGUE_ASSASSINATION: + builder = SPELL_MUTILATE, finisher = SPELL_ENVENOM; + VerifyAndPushSpellCast(spells, SPELL_COLD_BLOOD, TARGET_NONE, 20); + break; + case SPEC_ROGUE_COMBAT: + builder = SPELL_SINISTER_STRIKE, finisher = SPELL_EVISCERATE; + VerifyAndPushSpellCast(spells, SPELL_ADRENALINE_RUSH, TARGET_NONE, 6); + VerifyAndPushSpellCast(spells, SPELL_BLADE_FLURRY, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_KILLING_SPREE, TARGET_NONE, 25); + break; + case SPEC_ROGUE_SUBLETY: + builder = SPELL_HEMORRHAGE, finisher = SPELL_EVISCERATE; + VerifyAndPushSpellCast(spells, SPELL_PREPARATION, TARGET_NONE, 10); + if (!me->IsWithinMeleeRange(me->GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_SHADOWSTEP, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_DANCE, TARGET_NONE, 10); + break; + } + + if (Unit* victim = me->GetVictim()) + { + if (victim->HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_KICK, TARGET_VICTIM, 25); + + uint8 const cp = (me->GetComboTarget() == victim->GetGUID()) ? me->GetComboPoints() : 0; + if (cp >= 4) + VerifyAndPushSpellCast(spells, finisher, TARGET_VICTIM, 10); + if (cp <= 4) + VerifyAndPushSpellCast(spells, builder, TARGET_VICTIM, 5); + } + break; + } + case CLASS_PRIEST: + VerifyAndPushSpellCast(spells, SPELL_FEAR_WARD, TARGET_SELF, 2); + VerifyAndPushSpellCast(spells, SPELL_POWER_WORD_FORT, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_SPIRIT, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_PROTECTION, TARGET_CHARMER, 2); + VerifyAndPushSpellCast(spells, SPELL_DIVINE_HYMN, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_HYMN_OF_HOPE, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_WORD_DEATH, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_PSYCHIC_SCREAM, TARGET_VICTIM, 3); + switch (GetSpec()) + { + case SPEC_PRIEST_DISCIPLINE: + VerifyAndPushSpellCast(spells, SPELL_POWER_WORD_SHIELD, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_INNER_FOCUS, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_PAIN_SUPPRESSION, TARGET_CHARMER, 15); + VerifyAndPushSpellCast(spells, SPELL_POWER_INFUSION, TARGET_CHARMER, 10); + VerifyAndPushSpellCast(spells, SPELL_PENANCE, TARGET_CHARMER, 3); + VerifyAndPushSpellCast(spells, SPELL_FLASH_HEAL, TARGET_CHARMER, 1); + break; + case SPEC_PRIEST_HOLY: + VerifyAndPushSpellCast(spells, SPELL_DESPERATE_PRAYER, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_GUARDIAN_SPIRIT, TARGET_CHARMER, 5); + VerifyAndPushSpellCast(spells, SPELL_FLASH_HEAL, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_RENEW, TARGET_CHARMER, 3); + break; + case SPEC_PRIEST_SHADOW: + if (!me->HasAura(SPELL_SHADOWFORM)) + { + VerifyAndPushSpellCast(spells, SPELL_SHADOWFORM, TARGET_NONE, 100); + break; + } + if (Unit* victim = me->GetVictim()) + { + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_VAMPIRIC_TOUCH, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_VAMPIRIC_TOUCH, TARGET_VICTIM, 4); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_SHADOW_WORD_PAIN, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_SHADOW_WORD_PAIN, TARGET_VICTIM, 3); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_DEVOURING_PLAGUE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_DEVOURING_PLAGUE, TARGET_VICTIM, 4); + } + VerifyAndPushSpellCast(spells, SPELL_MIND_BLAST, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_MIND_FLAY, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_DISPERSION, TARGET_NONE, 10); + break; + } + break; + case CLASS_DEATH_KNIGHT: + { + if (!me->IsWithinMeleeRange(me->GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_DEATH_GRIP, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_STRANGULATE, TARGET_VICTIM, 15); + VerifyAndPushSpellCast(spells, SPELL_EMPOWER_RUNE_WEAP, TARGET_NONE, 5); + VerifyAndPushSpellCast(spells, SPELL_ICEBORN_FORTITUDE, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_ANTI_MAGIC_SHELL, TARGET_NONE, 10); + + bool hasFF = false, hasBP = false; + if (Unit* victim = me->GetVictim()) + { + if (victim->HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_MIND_FREEZE, TARGET_VICTIM, 25); + + hasFF = !!victim->GetAuraApplicationOfRankedSpell(AURA_FROST_FEVER, me->GetGUID()), hasBP = !!victim->GetAuraApplicationOfRankedSpell(AURA_BLOOD_PLAGUE, me->GetGUID()); + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_PESTILENCE, TARGET_VICTIM, 3); + if (!hasFF) + VerifyAndPushSpellCast(spells, SPELL_ICY_TOUCH, TARGET_VICTIM, 4); + if (!hasBP) + VerifyAndPushSpellCast(spells, SPELL_PLAGUE_STRIKE, TARGET_VICTIM, 4); + } + switch (GetSpec()) + { + case SPEC_DEATH_KNIGHT_BLOOD: + VerifyAndPushSpellCast(spells, SPELL_RUNE_TAP, TARGET_NONE, 2); + VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, TARGET_SELF, 5); + if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID())) + if (!creatureCharmer->IsDungeonBoss() && !creatureCharmer->isWorldBoss()) + VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, creatureCharmer, 15); + VerifyAndPushSpellCast(spells, SPELL_HEART_STRIKE, TARGET_VICTIM, 2); + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_DEATH_STRIKE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_DK, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_MARK_OF_BLOOD, TARGET_VICTIM, 20); + VerifyAndPushSpellCast(spells, SPELL_VAMPIRIC_BLOOD, TARGET_NONE, 10); + break; + case SPEC_DEATH_KNIGHT_FROST: + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_OBLITERATE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_HOWLING_BLAST, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_UNBREAKABLE_ARMOR, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_DEATHCHILL, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_FROST_STRIKE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_BLOOD_STRIKE, TARGET_VICTIM, 1); + break; + case SPEC_DEATH_KNIGHT_UNHOLY: + if (hasFF && hasBP) + VerifyAndPushSpellCast(spells, SPELL_SCOURGE_STRIKE, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_DEATH_AND_DECAY, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ANTI_MAGIC_ZONE, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_SUMMON_GARGOYLE, TARGET_VICTIM, 7); + VerifyAndPushSpellCast(spells, SPELL_BLOOD_STRIKE, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_DK, TARGET_VICTIM, 3); + break; + } + break; + } + case CLASS_SHAMAN: + VerifyAndPushSpellCast(spells, SPELL_HEROISM, TARGET_NONE, 25); + VerifyAndPushSpellCast(spells, SPELL_BLOODLUST, TARGET_NONE, 25); + VerifyAndPushSpellCast(spells, SPELL_GROUNDING_TOTEM, TARGET_NONE, 2); + switch (GetSpec()) + { + case SPEC_SHAMAN_RESTORATION: + if (Unit* charmer = me->GetCharmer()) + if (!charmer->GetAuraApplicationOfRankedSpell(SPELL_EARTH_SHIELD, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_EARTH_SHIELD, charmer, 2); + if (me->HasAura(SPELL_SHA_NATURE_SWIFT)) + VerifyAndPushSpellCast(spells, SPELL_HEALING_WAVE, TARGET_CHARMER, 20); + else + VerifyAndPushSpellCast(spells, SPELL_LESSER_HEAL_WAVE, TARGET_CHARMER, 1); + VerifyAndPushSpellCast(spells, SPELL_TIDAL_FORCE, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_SHA_NATURE_SWIFT, TARGET_NONE, 4); + VerifyAndPushSpellCast(spells, SPELL_MANA_TIDE_TOTEM, TARGET_NONE, 3); + break; + case SPEC_SHAMAN_ELEMENTAL: + if (Unit* victim = me->GetVictim()) + { + if (victim->GetAuraOfRankedSpell(SPELL_FLAME_SHOCK, GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_LAVA_BURST, TARGET_VICTIM, 5); + else + VerifyAndPushSpellCast(spells, SPELL_FLAME_SHOCK, TARGET_VICTIM, 3); + } + VerifyAndPushSpellCast(spells, SPELL_CHAIN_LIGHTNING, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LIGHTNING_BOLT, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_ELEMENTAL_MASTERY, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_THUNDERSTORM, TARGET_NONE, 3); + break; + case SPEC_SHAMAN_ENHANCEMENT: + if (Aura const* maelstrom = me->GetAura(AURA_MAELSTROM_WEAPON)) + if (maelstrom->GetStackAmount() == 5) + VerifyAndPushSpellCast(spells, SPELL_LIGHTNING_BOLT, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_STORMSTRIKE, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_EARTH_SHOCK, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_LAVA_LASH, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_SHAMANISTIC_RAGE, TARGET_NONE, 10); + break; + } + break; + case CLASS_MAGE: + if (me->GetVictim() && me->GetVictim()->HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_COUNTERSPELL, TARGET_VICTIM, 25); + VerifyAndPushSpellCast(spells, SPELL_DAMPEN_MAGIC, TARGET_CHARMER, 2); + VerifyAndPushSpellCast(spells, SPELL_EVOCATION, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_MANA_SHIELD, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_MIRROR_IMAGE, TARGET_NONE, 3); + VerifyAndPushSpellCast(spells, SPELL_SPELLSTEAL, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_ICE_BLOCK, TARGET_NONE, 1); + VerifyAndPushSpellCast(spells, SPELL_ICY_VEINS, TARGET_NONE, 3); + switch (GetSpec()) + { + case SPEC_MAGE_ARCANE: + if (Aura* abAura = me->GetAura(AURA_ARCANE_BLAST)) + if (abAura->GetStackAmount() >= 3) + VerifyAndPushSpellCast(spells, SPELL_ARCANE_MISSILES, TARGET_VICTIM, 7); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_BLAST, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_BARRAGE, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_ARCANE_POWER, TARGET_NONE, 8); + VerifyAndPushSpellCast(spells, SPELL_PRESENCE_OF_MIND, TARGET_NONE, 7); + break; + case SPEC_MAGE_FIRE: + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_LIVING_BOMB)) + VerifyAndPushSpellCast(spells, SPELL_LIVING_BOMB, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_COMBUSTION, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_FIREBALL, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_FIRE_BLAST, TARGET_VICTIM, 1); + VerifyAndPushSpellCast(spells, SPELL_DRAGONS_BREATH, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_BLAST_WAVE, TARGET_VICTIM, 1); + break; + case SPEC_MAGE_FROST: + VerifyAndPushSpellCast(spells, SPELL_DEEP_FREEZE, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_FROST_NOVA, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_FROSTBOLT, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_COLD_SNAP, TARGET_VICTIM, 5); + if (me->GetVictim() && me->GetVictim()->HasAuraState(AURA_STATE_FROZEN, nullptr, me)) + VerifyAndPushSpellCast(spells, SPELL_ICE_LANCE, TARGET_VICTIM, 5); + break; + } + break; + case CLASS_WARLOCK: + VerifyAndPushSpellCast(spells, SPELL_DEATH_COIL_W, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_FEAR, TARGET_VICTIM, 2); + VerifyAndPushSpellCast(spells, SPELL_SEED_OF_CORRUPTION, TARGET_VICTIM, 4); + VerifyAndPushSpellCast(spells, SPELL_HOWL_OF_TERROR, TARGET_NONE, 2); + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_CORRUPTION, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CORRUPTION, TARGET_VICTIM, 10); + switch (GetSpec()) + { + case SPEC_WARLOCK_AFFLICTION: + if (Unit* victim = me->GetVictim()) + { + VerifyAndPushSpellCast(spells, SPELL_SHADOW_BOLT, TARGET_VICTIM, 7); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_UNSTABLE_AFFLICTION, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_UNSTABLE_AFFLICTION, TARGET_VICTIM, 8); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_HAUNT, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_HAUNT, TARGET_VICTIM, 8); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_CURSE_OF_AGONY, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CURSE_OF_AGONY, TARGET_VICTIM, 4); + if (victim->HealthBelowPct(25)) + VerifyAndPushSpellCast(spells, SPELL_DRAIN_SOUL, TARGET_VICTIM, 100); + } + break; + case SPEC_WARLOCK_DEMONOLOGY: + VerifyAndPushSpellCast(spells, SPELL_METAMORPHOSIS, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_SHADOW_BOLT, TARGET_VICTIM, 7); + if (me->HasAura(AURA_DECIMATION)) + VerifyAndPushSpellCast(spells, SPELL_SOUL_FIRE, TARGET_VICTIM, 100); + if (me->HasAura(SPELL_METAMORPHOSIS)) + { + VerifyAndPushSpellCast(spells, SPELL_IMMOLATION_AURA, TARGET_NONE, 30); + if (!me->IsWithinMeleeRange(me->GetVictim())) + VerifyAndPushSpellCast(spells, SPELL_DEMON_CHARGE, TARGET_VICTIM, 20); + } + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_IMMOLATE, TARGET_VICTIM, 5); + if (me->HasAura(AURA_MOLTEN_CORE)) + VerifyAndPushSpellCast(spells, SPELL_INCINERATE, TARGET_VICTIM, 10); + break; + case SPEC_WARLOCK_DESTRUCTION: + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_IMMOLATE, TARGET_VICTIM, 8); + if (me->GetVictim() && me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_IMMOLATE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_CONFLAGRATE, TARGET_VICTIM, 8); + VerifyAndPushSpellCast(spells, SPELL_SHADOWFURY, TARGET_VICTIM, 5); + VerifyAndPushSpellCast(spells, SPELL_CHAOS_BOLT, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_SHADOWBURN, TARGET_VICTIM, 3); + VerifyAndPushSpellCast(spells, SPELL_INCINERATE, TARGET_VICTIM, 7); + break; + } + break; + case CLASS_DRUID: + VerifyAndPushSpellCast(spells, SPELL_INNERVATE, TARGET_CHARMER, 5); + VerifyAndPushSpellCast(spells, SPELL_BARKSKIN, TARGET_NONE, 5); + switch (GetSpec()) + { + case SPEC_DRUID_RESTORATION: + if (!me->HasAura(SPELL_TREE_OF_LIFE)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_TREE_OF_LIFE, TARGET_NONE, 100); + break; + } + VerifyAndPushSpellCast(spells, SPELL_TRANQUILITY, TARGET_NONE, 10); + VerifyAndPushSpellCast(spells, SPELL_NATURE_SWIFTNESS, TARGET_NONE, 7); + if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID())) + { + VerifyAndPushSpellCast(spells, SPELL_NOURISH, creatureCharmer, 5); + VerifyAndPushSpellCast(spells, SPELL_WILD_GROWTH, creatureCharmer, 5); + if (!creatureCharmer->GetAuraApplicationOfRankedSpell(SPELL_REJUVENATION, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_REJUVENATION, creatureCharmer, 8); + if (!creatureCharmer->GetAuraApplicationOfRankedSpell(SPELL_REGROWTH, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_REGROWTH, creatureCharmer, 8); + uint8 lifebloomStacks = 0; + if (Aura const* lifebloom = creatureCharmer->GetAura(SPELL_LIFEBLOOM, me->GetGUID())) + lifebloomStacks = lifebloom->GetStackAmount(); + if (lifebloomStacks < 3) + VerifyAndPushSpellCast(spells, SPELL_LIFEBLOOM, creatureCharmer, 5); + if (creatureCharmer->GetAuraApplicationOfRankedSpell(SPELL_REJUVENATION) || + creatureCharmer->GetAuraApplicationOfRankedSpell(SPELL_REGROWTH)) + VerifyAndPushSpellCast(spells, SPELL_SWIFTMEND, creatureCharmer, 10); + if (me->HasAura(SPELL_NATURE_SWIFTNESS)) + VerifyAndPushSpellCast(spells, SPELL_HEALING_TOUCH, creatureCharmer, 100); + } + break; + case SPEC_DRUID_BALANCE: + { + if (!me->HasAura(SPELL_MOONKIN_FORM)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_MOONKIN_FORM, TARGET_NONE, 100); + break; + } + uint32 const mainAttackSpell = me->HasAura(AURA_ECLIPSE_LUNAR) ? SPELL_STARFIRE : SPELL_WRATH; + VerifyAndPushSpellCast(spells, SPELL_STARFALL, TARGET_NONE, 20); + VerifyAndPushSpellCast(spells, mainAttackSpell, TARGET_VICTIM, 10); + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_INSECT_SWARM, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_INSECT_SWARM, TARGET_VICTIM, 7); + if (me->GetVictim() && !me->GetVictim()->GetAuraApplicationOfRankedSpell(SPELL_MOONFIRE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_MOONFIRE, TARGET_VICTIM, 5); + if (me->GetVictim() && me->GetVictim()->HasUnitState(UNIT_STATE_CASTING)) + VerifyAndPushSpellCast(spells, SPELL_TYPHOON, TARGET_NONE, 15); + break; + } + case SPEC_DRUID_FERAL: + if (!me->HasAura(SPELL_CAT_FORM)) + { + CancelAllShapeshifts(); + VerifyAndPushSpellCast(spells, SPELL_CAT_FORM, TARGET_NONE, 100); + break; + } + VerifyAndPushSpellCast(spells, SPELL_BERSERK, TARGET_NONE, 20); + VerifyAndPushSpellCast(spells, SPELL_SURVIVAL_INSTINCTS, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_TIGER_FURY, TARGET_NONE, 15); + VerifyAndPushSpellCast(spells, SPELL_DASH, TARGET_NONE, 5); + if (Unit* victim = me->GetVictim()) + { + uint8 const cp = (me->GetComboTarget() == victim->GetGUID()) ? me->GetComboPoints() : 0; + if (victim->HasUnitState(UNIT_STATE_CASTING) && cp >= 1) + VerifyAndPushSpellCast(spells, SPELL_MAIM, TARGET_VICTIM, 25); + if (!me->IsWithinMeleeRange(victim)) + VerifyAndPushSpellCast(spells, SPELL_FERAL_CHARGE_CAT, TARGET_VICTIM, 25); + if (cp >= 4) + VerifyAndPushSpellCast(spells, SPELL_RIP, TARGET_VICTIM, 50); + if (cp <= 4) + { + VerifyAndPushSpellCast(spells, SPELL_MANGLE_CAT, TARGET_VICTIM, 10); + VerifyAndPushSpellCast(spells, SPELL_CLAW, TARGET_VICTIM, 5); + if (!victim->GetAuraApplicationOfRankedSpell(SPELL_RAKE, me->GetGUID())) + VerifyAndPushSpellCast(spells, SPELL_RAKE, TARGET_VICTIM, 8); + if (!me->HasAura(SPELL_SAVAGE_ROAR)) + VerifyAndPushSpellCast(spells, SPELL_SAVAGE_ROAR, TARGET_NONE, 15); + } + } + break; + } + break; + } + + return SelectSpellCast(spells); +} + +static const float CASTER_CHASE_DISTANCE = 28.0f; +void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff) { Creature* charmer = me->GetCharmer() ? me->GetCharmer()->ToCreature() : nullptr; if (!charmer) @@ -192,10 +1286,54 @@ void SimpleCharmedPlayerAI::UpdateAI(const uint32 /*diff*/) return; if (IsRangedAttacker()) - AttackStartCaster(target, 28.0f); + { + _chaseCloser = !me->IsWithinLOSInMap(target); + if (_chaseCloser) + AttackStart(target); + else + AttackStartCaster(target, CASTER_CHASE_DISTANCE); + } else AttackStart(target); + _forceFacing = true; } + + if (me->IsStopped() && !me->HasUnitState(UNIT_STATE_CANNOT_TURN)) + { + float targetAngle = me->GetAngle(target); + if (_forceFacing || fabs(me->GetOrientation() - targetAngle) > 0.4f) + { + me->SetFacingTo(targetAngle); + _forceFacing = false; + } + } + + if (_castCheckTimer <= diff) + { + if (me->HasUnitState(UNIT_STATE_CASTING)) + _castCheckTimer = 0; + else + { + if (IsRangedAttacker()) + { // chase to zero if the target isn't in line of sight + bool inLOS = me->IsWithinLOSInMap(target); + if (_chaseCloser != !inLOS) + { + _chaseCloser = !inLOS; + if (_chaseCloser) + AttackStart(target); + else + AttackStartCaster(target, CASTER_CHASE_DISTANCE); + } + } + if (TargetedSpell shouldCast = SelectAppropriateCastForSpec()) + DoCastAtTarget(shouldCast); + _castCheckTimer = 500; + } + } + else + _castCheckTimer -= diff; + DoAutoAttackIfReady(); } else @@ -214,6 +1352,9 @@ void SimpleCharmedPlayerAI::OnCharmed(bool apply) { me->CastStop(); me->AttackStop(); + me->StopMoving(); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MovePoint(0, me->GetPosition(), false); // force re-sync of current position for all clients } else { diff --git a/src/server/game/AI/PlayerAI/PlayerAI.h b/src/server/game/AI/PlayerAI/PlayerAI.h index b717816f9a3..18f65485161 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.h +++ b/src/server/game/AI/PlayerAI/PlayerAI.h @@ -20,41 +20,99 @@ #include "UnitAI.h" #include "Player.h" +#include "Spell.h" #include "Creature.h" class TC_GAME_API PlayerAI : public UnitAI { public: - explicit PlayerAI(Player* player) : UnitAI(static_cast<Unit*>(player)), me(player), _isSelfHealer(PlayerAI::IsPlayerHealer(player)), _isSelfRangedAttacker(PlayerAI::IsPlayerRangedAttacker(player)) { } + explicit PlayerAI(Player* player) : UnitAI(static_cast<Unit*>(player)), me(player), _selfSpec(PlayerAI::GetPlayerSpec(player)), _isSelfHealer(PlayerAI::IsPlayerHealer(player)), _isSelfRangedAttacker(PlayerAI::IsPlayerRangedAttacker(player)) { } void OnCharmed(bool /*apply*/) override { } // charm AI application for players is handled by Unit::SetCharmedBy / Unit::RemoveCharmedBy // helper functions to determine player info + // Return values range from 0 (left-most spec) to 2 (right-most spec). If two specs have the same number of talent points, the left-most of those specs is returned. + static uint8 GetPlayerSpec(Player const* who); + // Return values range from 0 (left-most spec) to 2 (right-most spec). If two specs have the same number of talent points, the left-most of those specs is returned. + uint8 GetSpec(Player const* who = nullptr) const { return (!who || who == me) ? _selfSpec : GetPlayerSpec(who); } static bool IsPlayerHealer(Player const* who); bool IsHealer(Player const* who = nullptr) const { return (!who || who == me) ? _isSelfHealer : IsPlayerHealer(who); } static bool IsPlayerRangedAttacker(Player const* who); bool IsRangedAttacker(Player const* who = nullptr) const { return (!who || who == me) ? _isSelfRangedAttacker : IsPlayerRangedAttacker(who); } protected: + struct TargetedSpell : public std::pair<Spell*, Unit*> + { + TargetedSpell() : pair<Spell*, Unit*>() { } + TargetedSpell(Spell* first, Unit* second) : pair<Spell*, Unit*>(first, second) { } + explicit operator bool() { return !!first; } + }; + typedef std::pair<TargetedSpell, uint32> PossibleSpell; + typedef std::vector<PossibleSpell> PossibleSpellVector; + Player* const me; void SetIsRangedAttacker(bool state) { _isSelfRangedAttacker = state; } // this allows overriding of the default ranged attacker detection + enum SpellTarget + { + TARGET_NONE, + TARGET_VICTIM, + TARGET_CHARMER, + TARGET_SELF + }; + /* Check if the specified spell can be cast on that target. + Caller is responsible for cleaning up created Spell object from pointer. */ + TargetedSpell VerifySpellCast(uint32 spellId, Unit* target); + /* Check if the specified spell can be cast on that target. + Caller is responsible for cleaning up created Spell object from pointer. */ + TargetedSpell VerifySpellCast(uint32 spellId, SpellTarget target); + + /* Helper method - checks spell cast, then pushes it onto provided vector if valid. */ + template<typename T> inline void VerifyAndPushSpellCast(PossibleSpellVector& spells, uint32 spellId, T target, uint32 weight) + { + if (TargetedSpell spell = VerifySpellCast(spellId, target)) + spells.push_back({ spell,weight }); + } + + /* Helper method - selects one spell from the vector and returns it, while deleting everything else. + This invalidates the vector, and empties it to prevent accidental misuse. */ + TargetedSpell SelectSpellCast(PossibleSpellVector& spells); + /* Helper method - casts the included spell at the included target */ + inline void DoCastAtTarget(TargetedSpell spell) + { + SpellCastTargets targets; + targets.SetUnitTarget(spell.second); + spell.first->prepare(&targets); + } + virtual Unit* SelectAttackTarget() const { return me->GetCharmer() ? me->GetCharmer()->GetVictim() : nullptr; } void DoRangedAttackIfReady(); void DoAutoAttackIfReady(); + // Cancels all shapeshifts that the player could voluntarily cancel + void CancelAllShapeshifts(); + private: - bool _isSelfHealer; + uint8 const _selfSpec; + bool const _isSelfHealer; bool _isSelfRangedAttacker; }; class SimpleCharmedPlayerAI : public PlayerAI { public: - SimpleCharmedPlayerAI(Player* player) : PlayerAI(player) { } + SimpleCharmedPlayerAI(Player* player) : PlayerAI(player), _castCheckTimer(500), _chaseCloser(false), _forceFacing(true) { } void UpdateAI(uint32 diff) override; void OnCharmed(bool apply) override; + + protected: Unit* SelectAttackTarget() const override; + + private: + TargetedSpell SelectAppropriateCastForSpec(); + uint32 _castCheckTimer; + bool _chaseCloser; + bool _forceFacing; }; #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d2cbe482547..ec86e8fe4c3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2794,6 +2794,8 @@ void Player::GiveLevel(uint8 level) if (sWorld->getBoolConfig(CONFIG_ALWAYS_MAXSKILL)) // Max weapon skill when leveling up UpdateSkillsToMaxSkillsForLevel(); + _ApplyAllLevelScaleItemMods(true); + // set current level health and mana/energy to maximum after applying all mods. SetFullHealth(); SetPower(POWER_MANA, GetMaxPower(POWER_MANA)); @@ -2803,8 +2805,6 @@ void Player::GiveLevel(uint8 level) SetPower(POWER_FOCUS, 0); SetPower(POWER_HAPPINESS, 0); - _ApplyAllLevelScaleItemMods(true); - // update level to hunter/summon pet if (Pet* pet = GetPet()) pet->SynchronizeLevelWithOwner(); @@ -3202,10 +3202,9 @@ bool Player::AddTalent(uint32 spellId, uint8 spec, bool learning) } } - PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; PlayerTalent* newtalent = new PlayerTalent(); - newtalent->state = state; + newtalent->state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED; newtalent->spec = spec; (*m_talents[spec])[spellId] = newtalent; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 96bf207c274..2aa32374087 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -11653,8 +11653,11 @@ bool Unit::_IsValidAssistTarget(Unit const* target, SpellInfo const* bySpell) co && (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT))) return false; + // Controlled player case, we can assist creatures (reaction already checked above, our faction == charmer faction) + if (GetTypeId() == TYPEID_PLAYER && IsCharmed() && GetCharmerGUID().IsCreature()) + return true; // PvP case - if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) + else if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) { Player const* targetPlayerOwner = target->GetAffectingPlayer(); if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) @@ -13281,6 +13284,7 @@ void Unit::UpdateCharmAI() if (!newAI) // otherwise, we default to the generic one newAI = new SimpleCharmedPlayerAI(ToPlayer()); i_AI = newAI; + newAI->OnCharmed(true); } else { @@ -15574,7 +15578,11 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au { // change AI to charmed AI on next Update tick NeedChangeAI = true; - IsAIEnabled = false; + if (IsAIEnabled) + { + IsAIEnabled = false; + player->AI()->OnCharmed(true); + } } player->SetClientControl(this, false); } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 883f051b700..5d96de1def9 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2914,7 +2914,7 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->SetSpellModTakingSpell(this, true); - // Fill cost data (not use power for item casts + // Fill cost data (do not use power for item casts) m_powerCost = m_CastItem ? 0 : m_spellInfo->CalcPowerCost(m_caster, m_spellSchoolMask); if (m_caster->GetTypeId() == TYPEID_PLAYER) m_caster->ToPlayer()->SetSpellModTakingSpell(this, false); @@ -2992,7 +2992,7 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered // don't allow channeled spells / spells with cast time to be cast while moving // exception are only channeled spells that have no casttime and SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING // (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in) - if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) + if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && (!m_caster->IsCharmed() || !m_caster->GetCharmerGUID().IsCreature()) && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) { // 1. Is a channel spell, 2. Has no casttime, 3. And has flag to allow movement during channel if (!(m_spellInfo->IsChanneled() && !m_casttime && m_spellInfo->HasAttribute(SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING))) @@ -3546,7 +3546,13 @@ void Spell::update(uint32 difftime) { // don't cancel for melee, autorepeat, triggered and instant spells if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered() && !(IsChannelActive() && m_spellInfo->HasAttribute(SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING))) - cancel(); + { + // if charmed by creature, trust the AI not to cheat and allow the cast to proceed + // @todo this is a hack, "creature" movesplines don't differentiate turning/moving right now + // however, checking what type of movement the spline is for every single spline would be really expensive + if (!m_caster->GetCharmerGUID().IsCreature()) + cancel(); + } } switch (m_spellState) @@ -4842,7 +4848,7 @@ SpellCastResult Spell::CheckCast(bool strict) // cancel autorepeat spells if cast start when moving // (not wand currently autorepeat cast delayed to moving stop anyway in spell update code) - if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->isMoving()) + if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->isMoving() && (!m_caster->IsCharmed() || !m_caster->GetCharmerGUID().IsCreature())) { // skip stuck spell to allow use it in falling case and apply spell limitations at movement if ((!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR) || m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK) && @@ -5603,7 +5609,14 @@ SpellCastResult Spell::CheckPetCast(Unit* target) m_targets.SetUnitTarget(target); } - // cooldown + // check power requirement + // this would be zero until ::prepare normally, we set it here (it gets reset in ::prepare) + m_powerCost = m_spellInfo->CalcPowerCost(m_caster, m_spellSchoolMask); + SpellCastResult failReason = CheckPower(); + if (failReason != SPELL_CAST_OK) + return failReason; + + // check cooldown if (Creature* creatureCaster = m_caster->ToCreature()) if (!creatureCaster->GetSpellHistory()->IsReady(m_spellInfo)) return SPELL_FAILED_NOT_READY; @@ -5746,6 +5759,9 @@ SpellCastResult Spell::CheckCasterAuras() const bool Spell::CanAutoCast(Unit* target) { + if (!target) + return (CheckPetCast(target) == SPELL_CAST_OK); + ObjectGuid targetguid = target->GetGUID(); // check if target already has the same or a more powerful aura @@ -5782,16 +5798,19 @@ bool Spell::CanAutoCast(Unit* target) } SpellCastResult result = CheckPetCast(target); - if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT) { + // do not check targets for ground-targeted spells (we target them on top of the intended target anyway) + if (GetSpellInfo()->ExplicitTargetMask & TARGET_FLAG_DEST_LOCATION) + return true; SelectSpellTargets(); //check if among target units, our WANTED target is as well (->only self cast spells return false) - for (std::list<TargetInfo>::iterator ihit= m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) if (ihit->targetGUID == targetguid) return true; } - return false; //target invalid + // either the cast failed or the intended target wouldn't be hit + return false; } SpellCastResult Spell::CheckRange(bool strict) diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 56042e05257..70eb7ad76bf 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3750,6 +3750,8 @@ void SpellMgr::LoadSpellInfoCorrections() case 45257: // Using Steam Tonk Controller case 45440: // Steam Tonk Controller case 60256: // Collect Sample + case 45634: // Neural Needle + case 54897: // Flaming Arrow // Crashes client on pressing ESC spellInfo->AttributesEx4 &= ~SPELL_ATTR4_CAN_CAST_WHILE_CASTING; break; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index d84fe11383f..37457f36145 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -659,9 +659,8 @@ void World::LoadConfigSettings(bool reload) m_float_configs[CONFIG_GROUP_XP_DISTANCE] = sConfigMgr->GetFloatDefault("MaxGroupXPDistance", 74.0f); m_float_configs[CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE] = sConfigMgr->GetFloatDefault("MaxRecruitAFriendBonusDistance", 100.0f); - /// @todo Add MonsterSight and GuarderSight (with meaning) in worldserver.conf or put them as define + /// @todo Add MonsterSight (with meaning) in worldserver.conf or put them as define m_float_configs[CONFIG_SIGHT_MONSTER] = sConfigMgr->GetFloatDefault("MonsterSight", 50.0f); - m_float_configs[CONFIG_SIGHT_GUARDER] = sConfigMgr->GetFloatDefault("GuarderSight", 50.0f); if (reload) { diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 330e78cf510..4456b7a553e 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -183,7 +183,6 @@ enum WorldFloatConfigs CONFIG_GROUP_XP_DISTANCE = 0, CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE, CONFIG_SIGHT_MONSTER, - CONFIG_SIGHT_GUARDER, CONFIG_LISTEN_RANGE_SAY, CONFIG_LISTEN_RANGE_TEXTEMOTE, CONFIG_LISTEN_RANGE_YELL, |