aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormyran2 <henrytgordon@gmail.com>2016-04-29 11:49:07 -0400
committerShauren <shauren.trinity@gmail.com>2016-05-26 17:20:01 +0200
commitbc1a81747ae032bc2ae3681d99f5f6058d20caff (patch)
treebbec4aca0ee5f8a486cc64a12e91145d04816a4c
parent6c71c8694f91f6254e8a913f0550e4ff78cb6875 (diff)
Core/Pets: Implemented pet specializations (#17058)
* Use prepared statements in Pet::SavePetToDB * Add support for resetting all of a player's pet specializations * Send one big spell unlearn/learn packet instead of lots of small ones * Implemented Adaptation talent
-rw-r--r--sql/base/characters_database.sql3
-rw-r--r--sql/updates/characters/6.x/2016_05_26_00_characters.sql1
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp12
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h3
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp379
-rw-r--r--src/server/game/Entities/Pet/Pet.h18
-rw-r--r--src/server/game/Entities/Player/Player.cpp3
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp12
-rw-r--r--src/server/game/Handlers/PetHandler.cpp34
-rw-r--r--src/server/game/Server/Packets/PetPackets.cpp13
-rw-r--r--src/server/game/Server/Packets/PetPackets.h21
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp4
-rw-r--r--src/server/game/Server/WorldSession.h2
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp42
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.h2
-rw-r--r--src/server/game/Spells/SpellEffects.cpp4
16 files changed, 294 insertions, 259 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql
index bccc4176530..2d5104196b6 100644
--- a/sql/base/characters_database.sql
+++ b/sql/base/characters_database.sql
@@ -1071,6 +1071,7 @@ CREATE TABLE `character_pet` (
`curmana` int(10) unsigned NOT NULL DEFAULT '0',
`savetime` int(10) unsigned NOT NULL DEFAULT '0',
`abdata` text,
+ `specialization` smallint(5) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `owner` (`owner`),
KEY `idx_slot` (`slot`)
@@ -3109,7 +3110,7 @@ CREATE TABLE `updates` (
LOCK TABLES `updates` WRITE;
/*!40000 ALTER TABLE `updates` DISABLE KEYS */;
-INSERT INTO `updates` VALUES ('2014_10_20_00_characters.sql','A5882DA0979CF4DAE33DA011EBAA006C24BE7230','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_00_characters.sql','E2AC4758133EE19B7F08464A445802154D1261C8','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_01_characters.sql','20029E6323D9773B32C34D84FFED1711CC60F09F','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_02_characters.sql','8A7A16886EE71E7ACDDB3DDA6D0ECAC2FD2FDCA8','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_24_00_characters.sql','D008FE81AE844FCA686439D6ECC5108FB0DD1EB9','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_25_00_characters.sql','A39C7BE46686B54776BDAB9D7A882D91EDEC51A4','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_26_00_characters.sql','C787954CC35FE34B4101FDE6527F14C027F4947C','ARCHIVED','2015-03-21 15:55:55',0),('2014_11_12_00_characters.sql','B160BB2313F1BD5F3B076A5A9279DC10D4796E34','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_23_00_characters.sql','3D9D648B2387B357F4BD090B33F80682F7924882','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_28_00_characters.sql','5362922FF4483A336311D73082A5727309CD9219','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_31_00_characters.sql','498DDF2DD936CF156D74A8208DC93DCE9FCAB5AA','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_02_00_characters.sql','E5940BE836F253982E07930120422E598D08BDE1','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_10_00_characters.sql','30796056C8623699B2FE1BF626A19D38262E9284','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_16_00_characters.sql','96642760A54C8D799AAFE438049A63AA521656F2','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_27_00_characters.sql','EB710E3EB9F2CAFD84AB62CDC84E898403A80A4F','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_13_00_characters.sql','405BEB4ED207DC6076442A37EE2AFB1F21E274A0','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_13_01_characters.sql','35F582D4F33BF55D1685A1BA89273ED895FD09C5','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_17_00_characters.sql','8D21FC5A55BF8B55D6DCDCE5F02CF2B640230E94','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_10_00_characters.sql','E565B89B145C340067742DFF2DEF1B74F5F1BD4E','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_01_characters.sql','20BD68468C57FCF7E665B4DA185DCD52FACE8B3F','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_02_characters.sql','0296995DCD3676BA9AE6024CA7C91C5F39D927A3','ARCHIVED','2015-03-21 15:56:46',0),('2015_03_29_00_characters.sql','95D6A46BB746A8BD3EE3FE2086DF1A07F7C33B92','ARCHIVED','2015-05-02 15:43:06',0),('2015_04_21_00_characters.sql','F2032B9BF4EDA7EDE5065554724ED392FD91657D','ARCHIVED','2015-05-02 15:43:06',0),('2015_04_28_00_characters.sql','949F62DB3A3461D420A1230ECF7A6A3ED6435703','ARCHIVED','2015-05-02 15:43:06',0),('2015_05_08_00_characters.sql','0F14B7821618D1C872625B6EDDAA9A667B211167','ARCHIVED','2015-07-10 19:32:17',0),('2015_05_22_00_characters.sql','65B82152413FAB23BE413656E59A486A74447FF7','ARCHIVED','2015-07-10 19:32:17',0),('2015_07_08_00_characters.sql','DAB25360ACB5244C8F8E6214CF6BD97160588A5B','ARCHIVED','2015-07-10 19:32:17',0),('2015_07_11_00_characters.sql','B421B6C0E57BD0FD587071358863D9DABF4BA849','ARCHIVED','2015-07-13 21:50:02',0),('2015_07_12_00_characters.sql','E98E7FD61EF6426E7EDE8ED9AD8C15D8D7132589','ARCHIVED','2015-07-13 21:50:02',0),('2015_07_28_00_characters.sql','0711BC3A658D189EF71B0CB68DCFF2E9B781C4A0','ARCHIVED','2015-07-29 16:23:56',0),('2015_08_08_00_characters.sql','EA12BB2DC24FAF2300A96D0888A45BBEA158D5DC','ARCHIVED','2015-08-08 16:34:07',0),('2015_08_12_00_characters.sql','4FD7F89FE5DA51D4E0C33E520719986AA3EBD31B','ARCHIVED','2015-08-12 12:35:20',0),('2015_09_05_00_characters.sql','4C22BB29365BE4B6B95E64DAD84B63CA002304EA','ARCHIVED','2015-09-05 12:35:20',0),('2015_09_09_00_characters.sql','AFC32E693BC17CFD9A17919FE5317B8FE337ACAD','ARCHIVED','2015-09-09 12:35:20',0),('2015_09_10_00_characters.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-09-10 22:50:42',0),('2015_10_16_00_characters.sql','E3A3FFF0CB42F04A8DCF0CE4362143C16E2083AF','ARCHIVED','2015-10-15 21:54:11',0),('2015_11_06_00_characters_2015_10_12_00.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-11-06 23:43:27',0),('2015_11_08_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2015-11-08 00:51:45',15),('2015_11_23_00_characters.sql','9FC828E9E48E8E2E9B99A5A0073D6614C5BFC6B5','ARCHIVED','2015-11-22 23:27:34',0),('2016_01_05_00_characters.sql','0EAD24977F40DE2476B4567DA2B477867CC0DA1A','ARCHIVED','2016-01-04 23:07:40',0),('2016_04_05_00_characters_2016_02_10_00_characters.sql','F1B4DA202819CABC7319A4470A2D224A34609E97','ARCHIVED','2016-04-05 20:34:41',0),('2016_04_11_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','RELEASED','2016-04-11 02:24:14',30),('2016_04_11_01_characters.sql','CA90F6D99C1EEA7B25BD58BC8368A8D78234BBEF','RELEASED','2016-04-11 18:14:18',0),('2016_05_07_00_characters.sql','D1DB5557B21A552C935564D829B4E98B98149077','RELEASED','2016-05-07 00:00:00',0);
+INSERT INTO `updates` VALUES ('2014_10_20_00_characters.sql','A5882DA0979CF4DAE33DA011EBAA006C24BE7230','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_00_characters.sql','E2AC4758133EE19B7F08464A445802154D1261C8','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_01_characters.sql','20029E6323D9773B32C34D84FFED1711CC60F09F','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_23_02_characters.sql','8A7A16886EE71E7ACDDB3DDA6D0ECAC2FD2FDCA8','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_24_00_characters.sql','D008FE81AE844FCA686439D6ECC5108FB0DD1EB9','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_25_00_characters.sql','A39C7BE46686B54776BDAB9D7A882D91EDEC51A4','ARCHIVED','2015-03-21 15:55:55',0),('2014_10_26_00_characters.sql','C787954CC35FE34B4101FDE6527F14C027F4947C','ARCHIVED','2015-03-21 15:55:55',0),('2014_11_12_00_characters.sql','B160BB2313F1BD5F3B076A5A9279DC10D4796E34','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_23_00_characters.sql','3D9D648B2387B357F4BD090B33F80682F7924882','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_28_00_characters.sql','5362922FF4483A336311D73082A5727309CD9219','ARCHIVED','2015-03-21 15:55:55',0),('2014_12_31_00_characters.sql','498DDF2DD936CF156D74A8208DC93DCE9FCAB5AA','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_02_00_characters.sql','E5940BE836F253982E07930120422E598D08BDE1','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_10_00_characters.sql','30796056C8623699B2FE1BF626A19D38262E9284','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_16_00_characters.sql','96642760A54C8D799AAFE438049A63AA521656F2','ARCHIVED','2015-03-21 15:55:55',0),('2015_01_27_00_characters.sql','EB710E3EB9F2CAFD84AB62CDC84E898403A80A4F','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_13_00_characters.sql','405BEB4ED207DC6076442A37EE2AFB1F21E274A0','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_13_01_characters.sql','35F582D4F33BF55D1685A1BA89273ED895FD09C5','ARCHIVED','2015-03-21 15:55:55',0),('2015_02_17_00_characters.sql','8D21FC5A55BF8B55D6DCDCE5F02CF2B640230E94','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_10_00_characters.sql','E565B89B145C340067742DFF2DEF1B74F5F1BD4E','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_01_characters.sql','20BD68468C57FCF7E665B4DA185DCD52FACE8B3F','ARCHIVED','2015-03-21 15:55:55',0),('2015_03_20_02_characters.sql','0296995DCD3676BA9AE6024CA7C91C5F39D927A3','ARCHIVED','2015-03-21 15:56:46',0),('2015_03_29_00_characters.sql','95D6A46BB746A8BD3EE3FE2086DF1A07F7C33B92','ARCHIVED','2015-05-02 15:43:06',0),('2015_04_21_00_characters.sql','F2032B9BF4EDA7EDE5065554724ED392FD91657D','ARCHIVED','2015-05-02 15:43:06',0),('2015_04_28_00_characters.sql','949F62DB3A3461D420A1230ECF7A6A3ED6435703','ARCHIVED','2015-05-02 15:43:06',0),('2015_05_08_00_characters.sql','0F14B7821618D1C872625B6EDDAA9A667B211167','ARCHIVED','2015-07-10 19:32:17',0),('2015_05_22_00_characters.sql','65B82152413FAB23BE413656E59A486A74447FF7','ARCHIVED','2015-07-10 19:32:17',0),('2015_07_08_00_characters.sql','DAB25360ACB5244C8F8E6214CF6BD97160588A5B','ARCHIVED','2015-07-10 19:32:17',0),('2015_07_11_00_characters.sql','B421B6C0E57BD0FD587071358863D9DABF4BA849','ARCHIVED','2015-07-13 21:50:02',0),('2015_07_12_00_characters.sql','E98E7FD61EF6426E7EDE8ED9AD8C15D8D7132589','ARCHIVED','2015-07-13 21:50:02',0),('2015_07_28_00_characters.sql','0711BC3A658D189EF71B0CB68DCFF2E9B781C4A0','ARCHIVED','2015-07-29 16:23:56',0),('2015_08_08_00_characters.sql','EA12BB2DC24FAF2300A96D0888A45BBEA158D5DC','ARCHIVED','2015-08-08 16:34:07',0),('2015_08_12_00_characters.sql','4FD7F89FE5DA51D4E0C33E520719986AA3EBD31B','ARCHIVED','2015-08-12 12:35:20',0),('2015_09_05_00_characters.sql','4C22BB29365BE4B6B95E64DAD84B63CA002304EA','ARCHIVED','2015-09-05 12:35:20',0),('2015_09_09_00_characters.sql','AFC32E693BC17CFD9A17919FE5317B8FE337ACAD','ARCHIVED','2015-09-09 12:35:20',0),('2015_09_10_00_characters.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-09-10 22:50:42',0),('2015_10_16_00_characters.sql','E3A3FFF0CB42F04A8DCF0CE4362143C16E2083AF','ARCHIVED','2015-10-15 21:54:11',0),('2015_11_06_00_characters_2015_10_12_00.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-11-06 23:43:27',0),('2015_11_08_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2015-11-08 00:51:45',15),('2015_11_23_00_characters.sql','9FC828E9E48E8E2E9B99A5A0073D6614C5BFC6B5','ARCHIVED','2015-11-22 23:27:34',0),('2016_01_05_00_characters.sql','0EAD24977F40DE2476B4567DA2B477867CC0DA1A','ARCHIVED','2016-01-04 23:07:40',0),('2016_04_05_00_characters_2016_02_10_00_characters.sql','F1B4DA202819CABC7319A4470A2D224A34609E97','ARCHIVED','2016-04-05 20:34:41',0),('2016_04_11_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','RELEASED','2016-04-11 02:24:14',30),('2016_04_11_01_characters.sql','CA90F6D99C1EEA7B25BD58BC8368A8D78234BBEF','RELEASED','2016-04-11 18:14:18',0),('2016_05_07_00_characters.sql','D1DB5557B21A552C935564D829B4E98B98149077','RELEASED','2016-05-07 00:00:00',0),('2016_05_26_00_characters.sql','4179ADC32B96FD8D7D4CF5509A470B1ACE00BE85','RELEASED','2016-05-26 17:06:16',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;
diff --git a/sql/updates/characters/6.x/2016_05_26_00_characters.sql b/sql/updates/characters/6.x/2016_05_26_00_characters.sql
new file mode 100644
index 00000000000..41a5222ac16
--- /dev/null
+++ b/sql/updates/characters/6.x/2016_05_26_00_characters.sql
@@ -0,0 +1 @@
+ALTER TABLE `character_pet` ADD `specialization` smallint(5) unsigned NOT NULL DEFAULT '0' AFTER `abdata`;
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index a7e7394fc87..24f798f7f98 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -649,10 +649,10 @@ void CharacterDatabaseConnection::DoPrepareStatements()
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);
PrepareStatement(CHAR_INS_PET_AURA_EFFECT, "INSERT INTO pet_aura_effect (guid, casterGuid, spell, effectMask, effectIndex, amount, baseAmount) "
"VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ", CONNECTION_SYNCH);
- PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT_2, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND entry = ? AND (slot = ? OR slot > ?)", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PET_BY_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?) ", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT_EXCLUDE_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ? AND id <> ?", CONNECTION_ASYNC);
@@ -660,6 +660,10 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_SLOT, "DELETE FROM character_pet WHERE owner = ? AND (slot = ? OR slot > ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_ALL_PET_SPELLS_BY_OWNER, "DELETE FROM `pet_spell` WHERE `guid` in (SELECT `id` FROM `character_pet` WHERE owner=?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_UPD_PET_SPECS_BY_OWNER, "UPDATE character_pet SET specialization = 0 WHERE owner=?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType, specialization) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
// PvPstats
PrepareStatement(CHAR_SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds", CONNECTION_SYNCH);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index 18faea4c5b0..459b0dfe98d 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -559,6 +559,9 @@ enum CharacterDatabaseStatements
CHAR_UPD_CHAR_PET_SLOT_BY_ID,
CHAR_DEL_CHAR_PET_BY_ID,
CHAR_DEL_CHAR_PET_BY_SLOT,
+ CHAR_DEL_ALL_PET_SPELLS_BY_OWNER,
+ CHAR_UPD_PET_SPECS_BY_OWNER,
+ CHAR_INS_PET,
CHAR_SEL_ITEMCONTAINER_ITEMS,
CHAR_DEL_ITEMCONTAINER_ITEMS,
diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp
index deefff1423b..4678d3d0455 100644
--- a/src/server/game/Entities/Pet/Pet.cpp
+++ b/src/server/game/Entities/Pet/Pet.cpp
@@ -37,9 +37,9 @@
#define PET_XP_FACTOR 0.05f
Pet::Pet(Player* owner, PetType type) :
- Guardian(NULL, owner, true), m_usedTalentCount(0), m_removed(false),
+ Guardian(NULL, owner, true), m_removed(false),
m_petType(type), m_duration(0), m_loading(false), m_groupUpdateMask(0),
- m_declinedname(NULL)
+ m_declinedname(NULL), m_petSpecialization(0)
{
ASSERT(GetOwner());
@@ -312,8 +312,6 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
owner->SetMinion(this, true);
map->AddToMap(this->ToCreature());
- InitTalentForLevel(); // set original talents points before spell loading
-
uint32 timediff = uint32(time(NULL) - fields[13].GetUInt32());
_LoadAuras(timediff);
@@ -323,23 +321,28 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
m_charmInfo->LoadPetActionBar(fields[12].GetString());
_LoadSpells();
- InitTalentForLevel(); // re-init to check talent count
_LoadSpellCooldowns();
LearnPetPassives();
InitLevelupSpellsForLevel();
CastPetAuras(current);
}
- CleanupActionBar(); // remove unknown spells from action bar after load
-
TC_LOG_DEBUG("entities.pet", "New Pet has %s", GetGUID().ToString().c_str());
- owner->PetSpellInitialize();
+ uint16 specId = fields[16].GetUInt16();
+ if (ChrSpecializationEntry const* petSpec = sChrSpecializationStore.LookupEntry(specId))
+ specId = sChrSpecializationByIndexStore[owner->HasAuraType(SPELL_AURA_OVERRIDE_PET_SPECS) ? PET_SPEC_OVERRIDE_CLASS_INDEX : 0][petSpec->OrderIndex]->ID;
- SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL);
+ SetSpecialization(specId);
- // TODO: 6.x remove/update pet talents
- //owner->SendTalentsInfoData(true);
+ // The SetSpecialization function will run these functions if the pet's spec is not 0
+ if (!GetSpecialization())
+ {
+ CleanupActionBar(); // remove unknown spells from action bar after load
+ owner->PetSpellInitialize();
+ }
+
+ SetGroupUpdateFlag(GROUP_UPDATE_PET_FULL);
if (getPetType() == HUNTER_PET)
{
@@ -415,8 +418,6 @@ void Pet::SavePetToDB(PetSaveMode mode)
if (mode >= PET_SAVE_AS_CURRENT)
{
ObjectGuid::LowType ownerLowGUID = GetOwnerGUID().GetCounter();
- std::string name = m_name;
- CharacterDatabase.EscapeString(name);
trans = CharacterDatabase.BeginTransaction();
// remove current data
@@ -445,34 +446,28 @@ void Pet::SavePetToDB(PetSaveMode mode)
}
// save pet
- std::ostringstream ss;
- ss << "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType) "
- << "VALUES ("
- << m_charmInfo->GetPetNumber() << ','
- << GetEntry() << ','
- << ownerLowGUID << ','
- << GetNativeDisplayId() << ','
- << uint32(getLevel()) << ','
- << GetUInt32Value(UNIT_FIELD_PETEXPERIENCE) << ','
- << uint32(GetReactState()) << ','
- << uint32(mode) << ", '"
- << name.c_str() << "', "
- << uint32(HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) ? 0 : 1) << ','
- << curhealth << ','
- << curmana << ", '";
-
- for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i)
- {
- ss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' '
- << uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' ';
- };
-
- ss << "', "
- << time(NULL) << ','
- << GetUInt32Value(UNIT_CREATED_BY_SPELL) << ','
- << uint32(getPetType()) << ')';
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET);
+ stmt->setUInt32(0, m_charmInfo->GetPetNumber());
+ stmt->setUInt32(1, GetEntry());
+ stmt->setUInt64(2, ownerLowGUID);
+ stmt->setUInt32(3, GetNativeDisplayId());
+ stmt->setUInt8(4, getLevel());
+ stmt->setUInt32(5, GetUInt32Value(UNIT_FIELD_PETEXPERIENCE));
+ stmt->setUInt8(6, GetReactState());
+ stmt->setInt16(7, mode);
+ stmt->setString(8, m_name);
+ stmt->setUInt8(9, HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) ? 0 : 1);
+ stmt->setUInt32(10, curhealth);
+ stmt->setUInt32(11, curmana);
+
+ stmt->setString(12, GenerateActionBarData());
+
+ stmt->setUInt32(13, time(NULL));
+ stmt->setUInt32(14, GetUInt32Value(UNIT_CREATED_BY_SPELL));
+ stmt->setUInt8(15, getPetType());
+ stmt->setUInt16(16, m_petSpecialization);
+ trans->Append(stmt);
- trans->Append(ss.str().c_str());
CharacterDatabase.CommitTransaction(trans);
}
// delete
@@ -724,7 +719,6 @@ void Pet::GivePetLevel(uint8 level)
InitStatsForLevel(level);
InitLevelupSpellsForLevel();
- InitTalentForLevel();
}
bool Pet::CreateBaseAtCreature(Creature* creature)
@@ -1436,6 +1430,22 @@ bool Pet::learnSpell(uint32 spell_id)
return true;
}
+void Pet::learnSpells(std::vector<uint32> const& spellIds)
+{
+ WorldPackets::Pet::PetLearnedSpells packet;
+
+ for (uint32 spell : spellIds)
+ {
+ if (!addSpell(spell))
+ continue;
+
+ packet.Spells.push_back(spell);
+ }
+
+ if (!m_loading)
+ GetOwner()->GetSession()->SendPacket(packet.Write());
+}
+
void Pet::InitLevelupSpellsForLevel()
{
uint8 level = getLevel();
@@ -1488,6 +1498,22 @@ bool Pet::unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab)
return false;
}
+void Pet::unlearnSpells(std::vector<uint32> const& spellIds, bool learn_prev, bool clear_ab)
+{
+ WorldPackets::Pet::PetUnlearnedSpells packet;
+
+ for (uint32 spell : spellIds)
+ {
+ if (!removeSpell(spell, learn_prev, clear_ab))
+ continue;
+
+ packet.Spells.push_back(spell);
+ }
+
+ if (!m_loading)
+ GetOwner()->GetSession()->SendPacket(packet.Write());
+}
+
bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab)
{
PetSpellMap::iterator itr = m_spells.find(spell_id);
@@ -1513,7 +1539,7 @@ bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab)
}
// if remove last rank or non-ranked then update action bar at server and client if need
- if (clear_ab && !learn_prev && m_charmInfo->RemoveSpellFromActionBar(spell_id))
+ if (m_charmInfo->RemoveSpellFromActionBar(spell_id) && !learn_prev && clear_ab)
{
if (!m_loading)
GetOwner()->PetSpellInitialize(); // need update action bar for last removed rank
@@ -1549,182 +1575,6 @@ void Pet::InitPetCreateSpells()
CastPetAuras(false);
}
-bool Pet::resetTalents()
-{
- /* TODO: 6.x remove pet talents
- Player* player = GetOwner();
-
- // not need after this call
- if (player->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
- player->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true);
-
- CreatureTemplate const* ci = GetCreatureTemplate();
- if (!ci)
- return false;
- // Check pet talent type
- CreatureFamilyEntry const* pet_family = sCreatureFamilyStore.LookupEntry(ci->family);
- if (!pet_family || pet_family->PetTalentType < 0)
- return false;
-
- uint8 level = getLevel();
- uint32 talentPointsForLevel = GetMaxTalentPointsForLevel(level);
-
- if (m_usedTalentCount == 0)
- {
- SetFreeTalentPoints(talentPointsForLevel);
- return false;
- }
-
- for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
- {
- TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
-
- if (!talentInfo)
- continue;
-
- TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
-
- if (!talentTabInfo)
- continue;
-
- // unlearn only talents for pets family talent type
- if (!((1 << pet_family->PetTalentType) & talentTabInfo->petTalentMask))
- continue;
-
- for (uint8 j = 0; j < MAX_TALENT_RANK; ++j)
- {
- for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end();)
- {
- if (itr->second.state == PETSPELL_REMOVED)
- {
- ++itr;
- continue;
- }
- // remove learned spells (all ranks)
- uint32 itrFirstId = sSpellMgr->GetFirstSpellInChain(itr->first);
-
- // unlearn if first rank is talent or learned by talent
- if (itrFirstId == talentInfo->RankID[j] || sSpellMgr->IsSpellLearnToSpell(talentInfo->RankID[j], itrFirstId))
- {
- unlearnSpell(itr->first, false);
- itr = m_spells.begin();
- continue;
- }
- else
- ++itr;
- }
- }
- }
-
- SetFreeTalentPoints(talentPointsForLevel);
-
- if (!m_loading)
- player->PetSpellInitialize();*/
- return true;
-}
-
-void Pet::resetTalentsForAllPetsOf(Player* /*owner*/, Pet* /*onlinePet*/ /*= NULL*/)
-{
- /* TODO: 6.x remove pet talents
- // not need after this call
- if (owner->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
- owner->RemoveAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS, true);
-
- // reset for online
- if (onlinePet)
- onlinePet->resetTalents();
-
- // now need only reset for offline pets (all pets except online case)
- uint32 exceptPetNumber = onlinePet ? onlinePet->GetCharmInfo()->GetPetNumber() : 0;
-
- PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PET);
- stmt->setUInt64(0, owner->GetGUID().GetCounter());
- stmt->setUInt32(1, exceptPetNumber);
- PreparedQueryResult resultPets = CharacterDatabase.Query(stmt);
-
- // no offline pets
- if (!resultPets)
- return;
-
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_LIST);
- stmt->setUInt64(0, owner->GetGUID().GetCounter());
- stmt->setUInt32(1, exceptPetNumber);
- PreparedQueryResult result = CharacterDatabase.Query(stmt);
-
- if (!result)
- return;
-
- bool need_comma = false;
- std::ostringstream ss;
- ss << "DELETE FROM pet_spell WHERE guid IN (";
-
- do
- {
- Field* fields = resultPets->Fetch();
-
- uint32 id = fields[0].GetUInt32();
-
- if (need_comma)
- ss << ',';
-
- ss << id;
-
- need_comma = true;
- } while (resultPets->NextRow());
-
- ss << ") AND spell IN (";
-
- bool need_execute = false;
- do
- {
- Field* fields = result->Fetch();
-
- uint32 spell = fields[0].GetUInt32();
-
- if (!GetTalentSpellCost(spell))
- continue;
-
- if (need_execute)
- ss << ',';
-
- ss << spell;
-
- need_execute = true;
- }
- while (result->NextRow());
-
- if (!need_execute)
- return;
-
- ss << ')';
-
- CharacterDatabase.Execute(ss.str().c_str());*/
-}
-
-void Pet::InitTalentForLevel()
-{
- /* TODO: 6.x remove/update pet talents
- uint8 level = getLevel();
- uint32 talentPointsForLevel = GetMaxTalentPointsForLevel(level);
- // Reset talents in case low level (on level down) or wrong points for level (hunter can unlearn TP increase talent)
- if (talentPointsForLevel == 0 || m_usedTalentCount > talentPointsForLevel)
- resetTalents(); // Remove all talent points
-
- SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount);
-
- if (!m_loading)
- GetOwner()->SendTalentsInfoData(true);
- */
-}
-
-uint8 Pet::GetMaxTalentPointsForLevel(uint8 level) const
-{
- uint8 points = (level >= 20) ? ((level - 16) / 4) : 0;
- // Mod points from owner SPELL_AURA_MOD_PET_TALENT_POINTS
- points += GetOwner()->GetTotalAuraModifier(SPELL_AURA_MOD_PET_TALENT_POINTS);
- return points;
-}
-
void Pet::ToggleAutocast(SpellInfo const* spellInfo, bool apply)
{
ASSERT(spellInfo);
@@ -1940,3 +1790,96 @@ void Pet::ResetGroupUpdateFlag()
if (GetOwner()->GetGroup())
GetOwner()->RemoveGroupUpdateFlag(GROUP_UPDATE_FLAG_PET);
}
+
+void Pet::LearnSpecializationSpells()
+{
+ std::vector<uint32> learnedSpells;
+
+ if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(m_petSpecialization))
+ {
+ for (size_t j = 0; j < specSpells->size(); ++j)
+ {
+ SpecializationSpellsEntry const* specSpell = specSpells->at(j);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(specSpell->SpellID);
+ if (!spellInfo || spellInfo->SpellLevel > getLevel())
+ continue;
+
+ learnedSpells.push_back(specSpell->SpellID);
+ }
+ }
+
+ learnSpells(learnedSpells);
+}
+
+void Pet::RemoveSpecializationSpells(bool clearActionBar)
+{
+ std::vector<uint32> unlearnedSpells;
+
+ for (uint32 i = 0; i < MAX_SPECIALIZATIONS; ++i)
+ {
+ if (ChrSpecializationEntry const* specialization = sChrSpecializationByIndexStore[0][i])
+ {
+ if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(specialization->ID))
+ {
+ for (size_t j = 0; j < specSpells->size(); ++j)
+ {
+ SpecializationSpellsEntry const* specSpell = specSpells->at(j);
+ unlearnedSpells.push_back(specSpell->SpellID);
+ }
+ }
+ }
+
+ if (ChrSpecializationEntry const* specialization = sChrSpecializationByIndexStore[PET_SPEC_OVERRIDE_CLASS_INDEX][i])
+ {
+ if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(specialization->ID))
+ {
+ for (size_t j = 0; j < specSpells->size(); ++j)
+ {
+ SpecializationSpellsEntry const* specSpell = specSpells->at(j);
+ unlearnedSpells.push_back(specSpell->SpellID);
+ }
+ }
+ }
+ }
+
+ unlearnSpells(unlearnedSpells, true, clearActionBar);
+}
+
+void Pet::SetSpecialization(uint16 spec)
+{
+ if (m_petSpecialization == spec)
+ return;
+
+ // remove all the old spec's specalization spells, set the new spec, then add the new spec's spells
+ // clearActionBars is false because we'll be updating the pet actionbar later so we don't have to do it now
+ RemoveSpecializationSpells(false);
+ if (!sChrSpecializationStore.LookupEntry(spec))
+ {
+ m_petSpecialization = 0;
+ return;
+ }
+
+ m_petSpecialization = spec;
+ LearnSpecializationSpells();
+
+ // resend SMSG_PET_SPELLS_MESSAGE to remove old specialization spells from the pet action bar
+ CleanupActionBar();
+ GetOwner()->PetSpellInitialize();
+
+ WorldPackets::Pet::SetPetSpecialization setPetSpecialization;
+ setPetSpecialization.SpecID = m_petSpecialization;
+ GetOwner()->GetSession()->SendPacket(setPetSpecialization.Write());
+}
+
+std::string Pet::GenerateActionBarData() const
+{
+ std::ostringstream ss;
+
+ for (uint32 i = ACTION_BAR_INDEX_START; i < ACTION_BAR_INDEX_END; ++i)
+ {
+ ss << uint32(m_charmInfo->GetActionBarEntry(i)->GetType()) << ' '
+ << uint32(m_charmInfo->GetActionBarEntry(i)->GetAction()) << ' ';
+ }
+
+ return ss.str();
+}
diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h
index 170b881fa50..38d78ec181b 100644
--- a/src/server/game/Entities/Pet/Pet.h
+++ b/src/server/game/Entities/Pet/Pet.h
@@ -119,26 +119,24 @@ class TC_GAME_API Pet : public Guardian
bool addSpell(uint32 spellId, ActiveStates active = ACT_DECIDE, PetSpellState state = PETSPELL_NEW, PetSpellType type = PETSPELL_NORMAL);
bool learnSpell(uint32 spell_id);
+ void learnSpells(std::vector<uint32> const& spellIds);
void learnSpellHighRank(uint32 spellid);
void InitLevelupSpellsForLevel();
bool unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true);
+ void unlearnSpells(std::vector<uint32> const& spellIds, bool learn_prev, bool clear_ab = true);
bool removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true);
void CleanupActionBar();
+ std::string GenerateActionBarData() const;
PetSpellMap m_spells;
AutoSpellList m_autospells;
void InitPetCreateSpells();
- bool resetTalents();
- static void resetTalentsForAllPetsOf(Player* owner, Pet* online_pet = nullptr);
- void InitTalentForLevel();
-
- uint8 GetMaxTalentPointsForLevel(uint8 level) const;
- uint8 GetFreeTalentPoints() const { return GetByteValue(UNIT_FIELD_BYTES_1, 1); }
- void SetFreeTalentPoints(uint8 points) { SetByteValue(UNIT_FIELD_BYTES_1, 1, points); }
-
- uint32 m_usedTalentCount;
+ uint16 GetSpecialization() { return m_petSpecialization; }
+ void SetSpecialization(uint16 spec);
+ void LearnSpecializationSpells();
+ void RemoveSpecializationSpells(bool clearActionBar);
uint32 GetGroupUpdateFlag() const { return m_groupUpdateMask; }
void SetGroupUpdateFlag(uint32 flag);
@@ -159,6 +157,8 @@ class TC_GAME_API Pet : public Guardian
DeclinedName *m_declinedname;
+ uint16 m_petSpecialization;
+
private:
void SaveToDB(uint32, uint32, uint32) override // override of Creature::SaveToDB - must not be called
{
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 1efc4988a08..da964ef691f 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -20162,7 +20162,7 @@ void Player::PetSpellInitialize()
WorldPackets::Pet::PetSpells petSpellsPacket;
petSpellsPacket.PetGUID = pet->GetGUID();
petSpellsPacket._CreatureFamily = pet->GetCreatureTemplate()->family; // creature family (required for pet talents)
- //petSpellsPacket.Specialization = pet->GetSpecialization(); NYI
+ petSpellsPacket.Specialization = pet->GetSpecialization();
petSpellsPacket.TimeLimit = pet->GetDuration();
petSpellsPacket.ReactState = pet->GetReactState();
petSpellsPacket.CommandState = charmInfo->GetCommandState();
@@ -25954,7 +25954,6 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy
{
case SUMMON_PET:
pet->InitPetCreateSpells();
- pet->InitTalentForLevel();
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
PetSpellInitialize();
break;
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index ee75a4ed58c..95a1317b17c 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -1077,7 +1077,17 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
// reset for all pets before pet loading
if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_PET_TALENTS))
- Pet::resetTalentsForAllPetsOf(pCurrChar);
+ {
+ // Delete all of the player's pet spells
+ PreparedStatement* stmtSpells = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_PET_SPELLS_BY_OWNER);
+ stmtSpells->setUInt64(0, pCurrChar->GetGUID().GetCounter());
+ CharacterDatabase.Execute(stmtSpells);
+
+ // Then reset all of the player's pet specualizations
+ PreparedStatement* stmtSpec = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_PET_SPELLS_BY_OWNER);
+ stmtSpec->setUInt64(0, pCurrChar->GetGUID().GetCounter());
+ CharacterDatabase.Execute(stmtSpec);
+ }
// Load pet if any (if player not alive and in taxi flight or another then pet will remember as temporary unsummoned)
pCurrChar->LoadPet();
diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp
index a1e57ff087a..849a5097c61 100644
--- a/src/server/game/Handlers/PetHandler.cpp
+++ b/src/server/game/Handlers/PetHandler.cpp
@@ -722,3 +722,37 @@ void WorldSession::SendPetNameInvalid(uint32 error, const std::string& name, Dec
SendPacket(petNameInvalid.Write());
}
+
+void WorldSession::HandlePetSetSpecializationOpcode(WorldPackets::Pet::LearnPetSpecializationGroup& learnPetSpecializationGroup)
+{
+ if (!_player->IsInWorld())
+ return;
+
+ Pet* pet = ObjectAccessor::GetPet(*_player, learnPetSpecializationGroup.PetGUID);
+
+ if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET ||
+ pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo())
+ return;
+
+ if (learnPetSpecializationGroup.SpecGroupIndex >= MAX_SPECIALIZATIONS)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandlePetSetSpecializationOpcode - specialization index %u out of range", learnPetSpecializationGroup.SpecGroupIndex);
+ return;
+ }
+
+ uint32 specIndex = _player->HasAuraType(SPELL_AURA_OVERRIDE_PET_SPECS) ? PET_SPEC_OVERRIDE_CLASS_INDEX : 0;
+ ChrSpecializationEntry const* petSpec = sChrSpecializationByIndexStore[specIndex][learnPetSpecializationGroup.SpecGroupIndex];
+ if (!petSpec)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandlePetSetSpecializationOpcode - specialization index %u not found", learnPetSpecializationGroup.SpecGroupIndex);
+ return;
+ }
+
+ if (_player->getLevel() < MIN_SPECIALIZATION_LEVEL)
+ {
+ TC_LOG_DEBUG("network", "WORLD: HandlePetSetSpecializationOpcode - player level too low for specializations");
+ return;
+ }
+
+ pet->SetSpecialization(petSpec->ID);
+}
diff --git a/src/server/game/Server/Packets/PetPackets.cpp b/src/server/game/Server/Packets/PetPackets.cpp
index b07a74dfb10..36a71ba9c53 100644
--- a/src/server/game/Server/Packets/PetPackets.cpp
+++ b/src/server/game/Server/Packets/PetPackets.cpp
@@ -185,3 +185,16 @@ void WorldPackets::Pet::PetCancelAura::Read()
_worldPacket >> PetGUID;
_worldPacket >> SpellID;
}
+
+void WorldPackets::Pet::LearnPetSpecializationGroup::Read()
+{
+ _worldPacket >> PetGUID;
+ _worldPacket >> SpecGroupIndex;
+}
+
+WorldPacket const* WorldPackets::Pet::SetPetSpecialization::Write()
+{
+ _worldPacket << uint16(SpecID);
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/PetPackets.h b/src/server/game/Server/Packets/PetPackets.h
index 500fef3a249..f11a71a936c 100644
--- a/src/server/game/Server/Packets/PetPackets.h
+++ b/src/server/game/Server/Packets/PetPackets.h
@@ -226,6 +226,27 @@ namespace WorldPackets
int32 SpellID = 0;
};
+ class LearnPetSpecializationGroup final : public ClientPacket
+ {
+ public:
+ LearnPetSpecializationGroup(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_PET_SPECIALIZATION_GROUP, std::move(packet)) { }
+
+ void Read() override;
+
+ ObjectGuid PetGUID;
+ uint32 SpecGroupIndex = 0;
+ };
+
+ class SetPetSpecialization final : public ServerPacket
+ {
+ public:
+ SetPetSpecialization() : ServerPacket(SMSG_SET_PET_SPECIALIZATION, 2) { }
+
+ WorldPacket const* Write() override;
+
+ uint16 SpecID = 0;
+ };
+
}
}
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 44d05585373..c69d3613ada 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -430,7 +430,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_JOIN_RATED_BATTLEGROUND, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_KEEP_ALIVE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPacket, &WorldSession::Handle_EarlyProccess);
DEFINE_HANDLER(CMSG_KEYBOUND_OVERRIDE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_LEARN_PET_SPECIALIZATION_GROUP, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_LEARN_PET_SPECIALIZATION_GROUP, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::Pet::LearnPetSpecializationGroup, &WorldSession::HandlePetSetSpecializationOpcode);
DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Talent::LearnTalents, &WorldSession::HandleLearnTalentsOpcode);
DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Party::LeaveGroup, &WorldSession::HandleLeaveGroupOpcode);
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
@@ -1586,7 +1586,7 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_MELEE_ANIM_KIT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_MOVEMENT_ANIM_KIT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PCT_SPELL_MODIFIER, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PET_SPECIALIZATION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PET_SPECIALIZATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PLAY_HOVER_ANIM, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PROFICIENCY, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 3d661e1a8ff..b65a8b30d56 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -506,6 +506,7 @@ namespace WorldPackets
class PetAction;
class PetCancelAura;
class PetSetAction;
+ class LearnPetSpecializationGroup;
}
namespace Petition
@@ -1516,6 +1517,7 @@ class TC_GAME_API WorldSession
void HandlePetCancelAuraOpcode(WorldPackets::Spells::PetCancelAura& packet);
void HandlePetSpellAutocastOpcode(WorldPackets::Pet::PetSpellAutocast& packet);
void HandlePetCastSpellOpcode(WorldPackets::Spells::PetCastSpell& petCastSpell);
+ void HandlePetSetSpecializationOpcode(WorldPackets::Pet::LearnPetSpecializationGroup& learnPetSpecializationGroup);
void HandleSetActionBarToggles(WorldPackets::Character::SetActionBarToggles& packet);
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index 911db589f2d..d866691bda5 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -204,7 +204,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleAuraModBaseResistancePCT, //142 SPELL_AURA_MOD_BASE_RESISTANCE_PCT
&AuraEffect::HandleAuraModResistanceExclusive, //143 SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE
&AuraEffect::HandleNoImmediateEffect, //144 SPELL_AURA_SAFE_FALL implemented in WorldSession::HandleMovementOpcodes
- &AuraEffect::HandleAuraModPetTalentsPoints, //145 SPELL_AURA_MOD_PET_TALENT_POINTS
+ &AuraEffect::HandleNULL, //145 used by 5 spells in 6.2.4 dbc but the meaning of this aura changed (it's used by mind control spells but isn't the control itself)
&AuraEffect::HandleNoImmediateEffect, //146 SPELL_AURA_ALLOW_TAME_PET_TYPE
&AuraEffect::HandleModStateImmunityMask, //147 SPELL_AURA_MECHANIC_IMMUNITY_MASK
&AuraEffect::HandleAuraRetainComboPoints, //148 SPELL_AURA_RETAIN_COMBO_POINTS
@@ -510,7 +510,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNULL, //448
&AuraEffect::HandleNULL, //449
&AuraEffect::HandleNULL, //450
- &AuraEffect::HandleNULL, //451 SPELL_AURA_OVERRIDE_PET_SPECS
+ &AuraEffect::HandleOverridePetSpecs, //451 SPELL_AURA_OVERRIDE_PET_SPECS
&AuraEffect::HandleNULL, //452
&AuraEffect::HandleNoImmediateEffect, //453 SPELL_AURA_CHARGE_RECOVERY_MOD implemented in SpellHistory::GetChargeRecoveryTime
&AuraEffect::HandleNoImmediateEffect, //454 SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER implemented in SpellHistory::GetChargeRecoveryTime
@@ -2601,21 +2601,6 @@ void AuraEffect::HandleAuraUntrackable(AuraApplication const* aurApp, uint8 mode
/*** SKILLS & TALENTS ***/
/****************************/
-void AuraEffect::HandleAuraModPetTalentsPoints(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
-{
- if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
- return;
-
- Unit* target = aurApp->GetTarget();
-
- if (target->GetTypeId() != TYPEID_PLAYER)
- return;
-
- // Recalculate pet talent points
- if (Pet* pet = target->ToPlayer()->GetPet())
- pet->InitTalentForLevel();
-}
-
void AuraEffect::HandleAuraModSkill(AuraApplication const* aurApp, uint8 mode, bool apply) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_SKILL)))
@@ -6666,3 +6651,26 @@ void AuraEffect::HandleModSpellCategoryCooldown(AuraApplication const* aurApp, u
if (Player* player = aurApp->GetTarget()->ToPlayer())
player->SendSpellCategoryCooldowns();
}
+
+void AuraEffect::HandleOverridePetSpecs(AuraApplication const* aurApp, uint8 mode, bool apply) const
+{
+ if (!(mode & AURA_EFFECT_HANDLE_REAL))
+ return;
+
+ Player* player = aurApp->GetTarget()->ToPlayer();
+ if (!player)
+ return;
+
+ if (player->getClass() != CLASS_HUNTER)
+ return;
+
+ Pet* pet = player->GetPet();
+ if (!pet)
+ return;
+
+ ChrSpecializationEntry const* currSpec = sChrSpecializationStore.LookupEntry(pet->GetSpecialization());
+ if (!currSpec)
+ return;
+
+ pet->SetSpecialization(sChrSpecializationByIndexStore[apply ? PET_SPEC_OVERRIDE_CLASS_INDEX : 0][currSpec->OrderIndex]->ID);
+}
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h
index 0d907e4148c..512852a39ec 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.h
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.h
@@ -174,7 +174,6 @@ class TC_GAME_API AuraEffect
void HandleAuraModStalked(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraUntrackable(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// skills & talents
- void HandleAuraModPetTalentsPoints(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraModSkill(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// movement
void HandleAuraMounted(AuraApplication const* aurApp, uint8 mode, bool apply) const;
@@ -305,6 +304,7 @@ class TC_GAME_API AuraEffect
void HandleAuraForceWeather(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleEnableAltPower(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModSpellCategoryCooldown(AuraApplication const* aurApp, uint8 mode, bool apply) const;
+ void HandleOverridePetSpecs(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// aura effect periodic tick handlers
void HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const;
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 58f1f24e584..b72c03dc08a 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -2724,8 +2724,6 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/)
// caster have pet now
m_caster->SetMinion(pet, true);
- pet->InitTalentForLevel();
-
if (m_caster->GetTypeId() == TYPEID_PLAYER)
{
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
@@ -5265,8 +5263,6 @@ void Spell::EffectCreateTamedPet(SpellEffIndex /*effIndex*/)
// unitTarget has pet now
unitTarget->SetMinion(pet, true);
- pet->InitTalentForLevel();
-
if (unitTarget->GetTypeId() == TYPEID_PLAYER)
{
pet->SavePetToDB(PET_SAVE_AS_CURRENT);