aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy <Golrag@users.noreply.github.com>2018-03-04 22:32:08 +0100
committerShauren <shauren.trinity@gmail.com>2018-03-04 22:32:08 +0100
commitf411b7a90e4900c9e2df7c1e0c7a301360408cec (patch)
tree9793744cf0749d37bb13d6d69355b8672e5f6c34
parente0eafc1cac9d840f897f9269e8440aaebfab6a96 (diff)
Core/Player: Implement PvP Talents (#19962)
* SPELL_AURA_119 -> SPELL_AURA_PVP_TALENTS * Learn Honorable Medallion when pvp rules are enabled
-rw-r--r--sql/base/characters_database.sql27
-rw-r--r--sql/updates/characters/master/2018_03_04_00_characters.sql10
-rw-r--r--sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql44
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp3
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h3
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.cpp8
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.h5
-rw-r--r--src/server/game/DataStores/DB2LoadInfo.h39
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp43
-rw-r--r--src/server/game/DataStores/DB2Stores.h4
-rw-r--r--src/server/game/DataStores/DB2Structure.h23
-rw-r--r--src/server/game/DataStores/DBCEnums.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp344
-rw-r--r--src/server/game/Entities/Player/Player.h23
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp4
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp4
-rw-r--r--src/server/game/Handlers/DuelHandler.cpp2
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp24
-rw-r--r--src/server/game/Server/Packets/TalentPackets.cpp18
-rw-r--r--src/server/game/Server/Packets/TalentPackets.h22
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp6
-rw-r--r--src/server/game/Server/WorldSession.h2
-rw-r--r--src/server/game/Spells/Auras/SpellAuraDefines.h2
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp16
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.h3
25 files changed, 670 insertions, 11 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql
index 55abfac06c7..9e08c4a4a31 100644
--- a/sql/base/characters_database.sql
+++ b/sql/base/characters_database.sql
@@ -1087,6 +1087,30 @@ LOCK TABLES `character_pet_declinedname` WRITE;
UNLOCK TABLES;
--
+-- Table structure for table `character_pvp_talent`
+--
+
+DROP TABLE IF EXISTS `character_pvp_talent`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `character_pvp_talent` (
+ `guid` bigint(20) unsigned NOT NULL,
+ `talentId` mediumint(8) unsigned NOT NULL,
+ `talentGroup` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`guid`,`talentId`,`talentGroup`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `character_pvp_talent`
+--
+
+LOCK TABLES `character_pvp_talent` WRITE;
+/*!40000 ALTER TABLE `character_pvp_talent` DISABLE KEYS */;
+/*!40000 ALTER TABLE `character_pvp_talent` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
-- Table structure for table `character_queststatus`
--
@@ -3537,7 +3561,8 @@ INSERT INTO `updates` VALUES
('2017_10_29_00_characters.sql','8CFC473E7E87E58C317A72016BF69E9050D3BC83','ARCHIVED','2017-04-19 00:07:40',25),
('2018_02_03_00_characters.sql','73E9BFD848D7A22F2A7DD89CF64E30E3A8689512','ARCHIVED','2018-02-03 23:52:42',0),
('2018_02_08_00_characters.sql','75FA162A9B85D678B26F972371265F1EC2C75187','ARCHIVED','2018-02-08 22:23:28',0),
-('2018_02_19_00_characters.sql','75A0FFAFD0633921708DB0F72F9CC9796ACB960B','RELEASED','2018-02-19 22:33:32',117);
+('2018_02_19_00_characters.sql','75A0FFAFD0633921708DB0F72F9CC9796ACB960B','RELEASED','2018-02-19 22:33:32',117),
+('2018_03_04_00_characters.sql','2A4CD2EE2547E718490706FADC78BF36F0DED8D6','RELEASED','2018-03-04 18:15:24',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;
diff --git a/sql/updates/characters/master/2018_03_04_00_characters.sql b/sql/updates/characters/master/2018_03_04_00_characters.sql
new file mode 100644
index 00000000000..66ce84bad5e
--- /dev/null
+++ b/sql/updates/characters/master/2018_03_04_00_characters.sql
@@ -0,0 +1,10 @@
+--
+-- Table structure for table `character_pvp_talent`
+--
+DROP TABLE IF EXISTS `character_pvp_talent`;
+CREATE TABLE `character_pvp_talent` (
+ `guid` bigint(20) unsigned NOT NULL,
+ `talentId` mediumint(8) unsigned NOT NULL,
+ `talentGroup` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ PRIMARY KEY (`guid`,`talentId`,`talentGroup`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql b/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql
new file mode 100644
index 00000000000..ef7a3adfc49
--- /dev/null
+++ b/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql
@@ -0,0 +1,44 @@
+--
+-- Table structure for table `pvp_talent`
+--
+DROP TABLE IF EXISTS `pvp_talent`;
+CREATE TABLE `pvp_talent` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `Description` text,
+ `SpellID` int(10) NOT NULL DEFAULT '0',
+ `OverridesSpellID` int(10) NOT NULL DEFAULT '0',
+ `ExtraSpellID` int(10) NOT NULL DEFAULT '0',
+ `TierID` int(10) NOT NULL DEFAULT '0',
+ `ColumnIndex` int(10) NOT NULL DEFAULT '0',
+ `Flags` int(10) NOT NULL DEFAULT '0',
+ `ClassID` int(10) NOT NULL DEFAULT '0',
+ `SpecID` int(10) NOT NULL DEFAULT '0',
+ `Role` int(10) NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+--
+-- Table structure for table `pvp_talent_locale`
+--
+DROP TABLE IF EXISTS `pvp_talent_locale`;
+CREATE TABLE `pvp_talent_locale` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `Description_lang` text,
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+--
+-- Table structure for table `pvp_talent_unlock`
+--
+DROP TABLE IF EXISTS `pvp_talent_unlock`;
+CREATE TABLE `pvp_talent_unlock` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `TierID` int(10) NOT NULL DEFAULT '0',
+ `ColumnIndex` int(10) NOT NULL DEFAULT '0',
+ `HonorLevel` int(10) NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 698d3a4c6e9..c366956f4cb 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -139,6 +139,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell FROM character_battleground_data WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_CHARACTER_PVP_TALENTS, "SELECT talentId, talentGroup FROM character_pvp_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
@@ -590,6 +591,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_CHAR_PVP_TALENT, "DELETE FROM character_pvp_talent WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, button, action, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC);
@@ -622,6 +624,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_CHAR_PVP_TALENT, "INSERT INTO character_pvp_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index 3107f437fbf..c4c081ab73a 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -112,6 +112,7 @@ enum CharacterDatabaseStatements : uint32
CHAR_SEL_CHARACTER_BGDATA,
CHAR_SEL_CHARACTER_GLYPHS,
CHAR_SEL_CHARACTER_TALENTS,
+ CHAR_SEL_CHARACTER_PVP_TALENTS,
CHAR_SEL_CHARACTER_SKILLS,
CHAR_SEL_CHARACTER_RANDOMBG,
CHAR_SEL_CHARACTER_BANNED,
@@ -491,6 +492,7 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_GUILD_BANK_EVENTLOG_BY_PLAYER,
CHAR_DEL_CHAR_GLYPHS,
CHAR_DEL_CHAR_TALENT,
+ CHAR_DEL_CHAR_PVP_TALENT,
CHAR_DEL_CHAR_SKILLS,
CHAR_UPD_CHAR_MONEY,
CHAR_INS_CHAR_ACTION,
@@ -521,6 +523,7 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_PETITION_SIGNATURE_BY_OWNER,
CHAR_INS_CHAR_GLYPHS,
CHAR_INS_CHAR_TALENT,
+ CHAR_INS_CHAR_PVP_TALENT,
CHAR_UPD_CHAR_LIST_SLOT,
CHAR_INS_CHAR_FISHINGSTEPS,
CHAR_DEL_CHAR_FISHINGSTEPS,
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp
index 18119e98096..0bca8efafb7 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.cpp
+++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp
@@ -703,6 +703,14 @@ void HotfixDatabaseConnection::DoPrepareStatements()
// PvpReward.db2
PrepareStatement(HOTFIX_SEL_PVP_REWARD, "SELECT ID, HonorLevel, Prestige, RewardPackID FROM pvp_reward ORDER BY ID DESC", CONNECTION_SYNCH);
+ // PvpTalent.db2
+ PrepareStatement(HOTFIX_SEL_PVP_TALENT, "SELECT ID, Description, SpellID, OverridesSpellID, ExtraSpellID, TierID, ColumnIndex, Flags, ClassID, "
+ "SpecID, Role FROM pvp_talent ORDER BY ID DESC", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_PVP_TALENT, "SELECT ID, Description_lang FROM pvp_talent_locale WHERE locale = ?", CONNECTION_SYNCH);
+
+ // PvpTalentUnlock.db2
+ PrepareStatement(HOTFIX_SEL_PVP_TALENT_UNLOCK, "SELECT ID, TierID, ColumnIndex, HonorLevel FROM pvp_talent_unlock ORDER BY ID DESC", CONNECTION_SYNCH);
+
// QuestFactionReward.db2
PrepareStatement(HOTFIX_SEL_QUEST_FACTION_REWARD, "SELECT ID, QuestRewFactionValue1, QuestRewFactionValue2, QuestRewFactionValue3, "
"QuestRewFactionValue4, QuestRewFactionValue5, QuestRewFactionValue6, QuestRewFactionValue7, QuestRewFactionValue8, QuestRewFactionValue9, "
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h
index d8fcbe9ab11..eb8d0e9c596 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.h
+++ b/src/server/database/Database/Implementation/HotfixDatabase.h
@@ -375,6 +375,11 @@ enum HotfixDatabaseStatements : uint32
HOTFIX_SEL_PVP_REWARD,
+ HOTFIX_SEL_PVP_TALENT,
+ HOTFIX_SEL_PVP_TALENT_LOCALE,
+
+ HOTFIX_SEL_PVP_TALENT_UNLOCK,
+
HOTFIX_SEL_QUEST_FACTION_REWARD,
HOTFIX_SEL_QUEST_MONEY_REWARD,
diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h
index ba065ad4fbe..8c090977f7c 100644
--- a/src/server/game/DataStores/DB2LoadInfo.h
+++ b/src/server/game/DataStores/DB2LoadInfo.h
@@ -3544,6 +3544,45 @@ struct PvpRewardLoadInfo
}
};
+struct PvpTalentLoadInfo
+{
+ static DB2LoadInfo const* Instance()
+ {
+ static DB2FieldMeta const fields[] =
+ {
+ { false, FT_INT, "ID" },
+ { false, FT_STRING, "Description" },
+ { true, FT_INT, "SpellID" },
+ { true, FT_INT, "OverridesSpellID" },
+ { true, FT_INT, "ExtraSpellID" },
+ { true, FT_INT, "TierID" },
+ { true, FT_INT, "ColumnIndex" },
+ { true, FT_INT, "Flags" },
+ { true, FT_INT, "ClassID" },
+ { true, FT_INT, "SpecID" },
+ { true, FT_INT, "Role" },
+ };
+ static DB2LoadInfo const loadInfo(&fields[0], std::extent<decltype(fields)>::value, PvpTalentMeta::Instance(), HOTFIX_SEL_PVP_TALENT);
+ return &loadInfo;
+ }
+};
+
+struct PvpTalentUnlockLoadInfo
+{
+ static DB2LoadInfo const* Instance()
+ {
+ static DB2FieldMeta const fields[] =
+ {
+ { false, FT_INT, "ID" },
+ { true, FT_INT, "TierID" },
+ { true, FT_INT, "ColumnIndex" },
+ { true, FT_INT, "HonorLevel" },
+ };
+ static DB2LoadInfo const loadInfo(&fields[0], std::extent<decltype(fields)>::value, PvpTalentUnlockMeta::Instance(), HOTFIX_SEL_PVP_TALENT_UNLOCK);
+ return &loadInfo;
+ }
+};
+
struct QuestFactionRewardLoadInfo
{
static DB2LoadInfo const* Instance()
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 4c7502c337a..a002b03c9c4 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -183,6 +183,8 @@ DB2Storage<PowerTypeEntry> sPowerTypeStore("PowerType.db2",
DB2Storage<PrestigeLevelInfoEntry> sPrestigeLevelInfoStore("PrestigeLevelInfo.db2", PrestigeLevelInfoLoadInfo::Instance());
DB2Storage<PVPDifficultyEntry> sPVPDifficultyStore("PVPDifficulty.db2", PvpDifficultyLoadInfo::Instance());
DB2Storage<PvpRewardEntry> sPvpRewardStore("PvpReward.db2", PvpRewardLoadInfo::Instance());
+DB2Storage<PvpTalentEntry> sPvpTalentStore("PvpTalent.db2", PvpTalentLoadInfo::Instance());
+DB2Storage<PvpTalentUnlockEntry> sPvpTalentUnlockStore("PvpTalentUnlock.db2", PvpTalentUnlockLoadInfo::Instance());
DB2Storage<QuestFactionRewardEntry> sQuestFactionRewardStore("QuestFactionReward.db2", QuestFactionRewardLoadInfo::Instance());
DB2Storage<QuestMoneyRewardEntry> sQuestMoneyRewardStore("QuestMoneyReward.db2", QuestMoneyRewardLoadInfo::Instance());
DB2Storage<QuestPackageItemEntry> sQuestPackageItemStore("QuestPackageItem.db2", QuestPackageItemLoadInfo::Instance());
@@ -308,6 +310,7 @@ typedef std::unordered_map<uint32, std::array<std::vector<NameGenEntry const*>,
typedef std::array<std::vector<Trinity::wregex>, TOTAL_LOCALES + 1> NameValidationRegexContainer;
typedef std::unordered_map<uint32, std::set<uint32>> PhaseGroupContainer;
typedef std::array<PowerTypeEntry const*, MAX_POWERS> PowerTypesContainer;
+typedef std::vector<PvpTalentEntry const*> PvpTalentsByPosition[MAX_CLASSES][MAX_PVP_TALENT_TIERS][MAX_PVP_TALENT_COLUMNS];
typedef std::unordered_map<uint32, std::pair<std::vector<QuestPackageItemEntry const*>, std::vector<QuestPackageItemEntry const*>>> QuestPackageItemContainer;
typedef std::unordered_map<uint32, uint32> RulesetItemUpgradeContainer;
typedef std::unordered_multimap<uint32, SkillRaceClassInfoEntry const*> SkillRaceClassInfoContainer;
@@ -362,6 +365,8 @@ namespace
PhaseGroupContainer _phasesByGroup;
PowerTypesContainer _powerTypes;
std::unordered_map<std::pair<uint32 /*prestige level*/, uint32 /*honor level*/>, uint32> _pvpRewardPack;
+ PvpTalentsByPosition _pvpTalentsByPosition;
+ uint32 _pvpTalentUnlock[MAX_PVP_TALENT_TIERS][MAX_PVP_TALENT_COLUMNS];
QuestPackageItemContainer _questPackages;
std::unordered_map<uint32, std::vector<RewardPackXItemEntry const*>> _rewardPackItems;
RulesetItemUpgradeContainer _rulesetItemUpgrade;
@@ -608,6 +613,8 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
LOAD_DB2(sPrestigeLevelInfoStore);
LOAD_DB2(sPVPDifficultyStore);
LOAD_DB2(sPvpRewardStore);
+ LOAD_DB2(sPvpTalentStore);
+ LOAD_DB2(sPvpTalentUnlockStore);
LOAD_DB2(sQuestFactionRewardStore);
LOAD_DB2(sQuestMoneyRewardStore);
LOAD_DB2(sQuestPackageItemStore);
@@ -943,6 +950,27 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
for (PvpRewardEntry const* pvpReward : sPvpRewardStore)
_pvpRewardPack[std::make_pair(pvpReward->Prestige, pvpReward->HonorLevel)] = pvpReward->RewardPackID;
+ for (PvpTalentEntry const* talentInfo : sPvpTalentStore)
+ {
+ ASSERT(talentInfo->ClassID < MAX_CLASSES);
+ ASSERT(talentInfo->TierID < MAX_PVP_TALENT_TIERS, "MAX_PVP_TALENT_TIERS must be at least %u", talentInfo->TierID + 1);
+ ASSERT(talentInfo->ColumnIndex < MAX_PVP_TALENT_COLUMNS, "MAX_PVP_TALENT_COLUMNS must be at least %u", talentInfo->ColumnIndex + 1);
+ if (!talentInfo->ClassID)
+ {
+ for (uint32 i = 1; i < MAX_CLASSES; ++i)
+ _pvpTalentsByPosition[i][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo);
+ }
+ else
+ _pvpTalentsByPosition[talentInfo->ClassID][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo);
+ }
+
+ for (PvpTalentUnlockEntry const* talentUnlock : sPvpTalentUnlockStore)
+ {
+ ASSERT(talentUnlock->TierID < MAX_PVP_TALENT_TIERS, "MAX_PVP_TALENT_TIERS must be at least %u", talentUnlock->TierID + 1);
+ ASSERT(talentUnlock->ColumnIndex < MAX_PVP_TALENT_COLUMNS, "MAX_PVP_TALENT_COLUMNS must be at least %u", talentUnlock->ColumnIndex + 1);
+ _pvpTalentUnlock[talentUnlock->TierID][talentUnlock->ColumnIndex] = talentUnlock->HonorLevel;
+ }
+
for (QuestPackageItemEntry const* questPackageItem : sQuestPackageItemStore)
{
if (questPackageItem->FilterType != QUEST_PACKAGE_FILTER_UNMATCHED)
@@ -993,8 +1021,8 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
for (TalentEntry const* talentInfo : sTalentStore)
{
ASSERT(talentInfo->ClassID < MAX_CLASSES);
- ASSERT(talentInfo->TierID < MAX_TALENT_TIERS, "MAX_TALENT_TIERS must be at least %u", talentInfo->TierID);
- ASSERT(talentInfo->ColumnIndex < MAX_TALENT_COLUMNS, "MAX_TALENT_COLUMNS must be at least %u", talentInfo->ColumnIndex);
+ ASSERT(talentInfo->TierID < MAX_TALENT_TIERS, "MAX_TALENT_TIERS must be at least %u", talentInfo->TierID + 1);
+ ASSERT(talentInfo->ColumnIndex < MAX_TALENT_COLUMNS, "MAX_TALENT_COLUMNS must be at least %u", talentInfo->ColumnIndex + 1);
_talentsByPosition[talentInfo->ClassID][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo);
}
@@ -1895,6 +1923,17 @@ uint32 DB2Manager::GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(uint8 hono
return itr->second;
}
+uint32 DB2Manager::GetRequiredHonorLevelForPvpTalent(PvpTalentEntry const* talentInfo) const
+{
+ ASSERT(talentInfo);
+ return _pvpTalentUnlock[talentInfo->TierID][talentInfo->ColumnIndex];
+}
+
+std::vector<PvpTalentEntry const*> const& DB2Manager::GetPvpTalentsByPosition(uint32 class_, uint32 tier, uint32 column) const
+{
+ return _pvpTalentsByPosition[class_][tier][column];
+}
+
std::vector<QuestPackageItemEntry const*> const* DB2Manager::GetQuestPackageItems(uint32 questPackageID) const
{
auto itr = _questPackages.find(questPackageID);
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index 60c3990ffc9..05d91ebea6a 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -145,6 +145,8 @@ TC_GAME_API extern DB2Storage<OverrideSpellDataEntry> sOverrideSpe
TC_GAME_API extern DB2Storage<PhaseEntry> sPhaseStore;
TC_GAME_API extern DB2Storage<PlayerConditionEntry> sPlayerConditionStore;
TC_GAME_API extern DB2Storage<PowerDisplayEntry> sPowerDisplayStore;
+TC_GAME_API extern DB2Storage<PvpTalentEntry> sPvpTalentStore;
+TC_GAME_API extern DB2Storage<PvpTalentUnlockEntry> sPvpTalentUnlockStore;
TC_GAME_API extern DB2Storage<QuestFactionRewardEntry> sQuestFactionRewardStore;
TC_GAME_API extern DB2Storage<QuestMoneyRewardEntry> sQuestMoneyRewardStore;
TC_GAME_API extern DB2Storage<QuestSortEntry> sQuestSortStore;
@@ -307,6 +309,8 @@ public:
static PVPDifficultyEntry const* GetBattlegroundBracketByLevel(uint32 mapid, uint32 level);
static PVPDifficultyEntry const* GetBattlegroundBracketById(uint32 mapid, BattlegroundBracketId id);
uint32 GetRewardPackIDForPvpRewardByHonorLevelAndPrestige(uint8 honorLevel, uint8 prestige) const;
+ uint32 GetRequiredHonorLevelForPvpTalent(PvpTalentEntry const* talentInfo) const;
+ std::vector<PvpTalentEntry const*> const& GetPvpTalentsByPosition(uint32 class_, uint32 tier, uint32 column) const;
std::vector<QuestPackageItemEntry const*> const* GetQuestPackageItems(uint32 questPackageID) const;
std::vector<QuestPackageItemEntry const*> const* GetQuestPackageItemsFallback(uint32 questPackageID) const;
uint32 GetQuestUniqueBitFlag(uint32 questId);
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index b45c0fb74ed..9148ab9070d 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -2115,6 +2115,29 @@ struct PvpRewardEntry
uint32 RewardPackID;
};
+struct PvpTalentEntry
+{
+ uint32 ID;
+ LocalizedString* Description;
+ int32 SpellID;
+ int32 OverridesSpellID;
+ int32 ExtraSpellID;
+ int32 TierID;
+ int32 ColumnIndex;
+ int32 Flags;
+ int32 ClassID;
+ int32 SpecID;
+ int32 Role;
+};
+
+struct PvpTalentUnlockEntry
+{
+ uint32 ID;
+ int32 TierID;
+ int32 ColumnIndex;
+ int32 HonorLevel;
+};
+
struct QuestFactionRewardEntry
{
uint32 ID;
diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h
index 31fdaaa7bff..5ea1ff3d2a8 100644
--- a/src/server/game/DataStores/DBCEnums.h
+++ b/src/server/game/DataStores/DBCEnums.h
@@ -961,6 +961,8 @@ enum SummonPropFlags
#define MAX_TALENT_TIERS 7
#define MAX_TALENT_COLUMNS 3
+#define MAX_PVP_TALENT_TIERS 6
+#define MAX_PVP_TALENT_COLUMNS 3
enum TaxiNodeFlags
{
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 139bac0dbb0..d41aa8c75dc 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -1078,6 +1078,11 @@ void Player::Update(uint32 p_time)
UpdateAfkReport(now);
+ if (GetCombatTimer()) // Only set when in pvp combat
+ if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
+ if (!aura->IsPermanent())
+ aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
+
if (IsAIEnabled && GetAI())
GetAI()->UpdateAI(p_time);
else if (NeedChangeAI)
@@ -2436,6 +2441,9 @@ void Player::GiveLevel(uint8 level)
InitTalentForLevel();
InitTaxiNodesForLevel();
+ if (level < PLAYER_LEVEL_MIN_HONOR)
+ ResetPvpTalents();
+
UpdateAllStats();
if (sWorld->getBoolConfig(CONFIG_ALWAYS_MAXSKILL)) // Max weapon skill when leveling up
@@ -3539,6 +3547,26 @@ bool Player::ResetTalents(bool noCost)
return true;
}
+void Player::ResetPvpTalents()
+{
+ for (uint32 talentId = 0; talentId < sPvpTalentStore.GetNumRows(); ++talentId)
+ {
+ PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentId);
+ if (!talentInfo)
+ continue;
+
+ if (talentInfo->ClassID && talentInfo->ClassID != getClass())
+ continue;
+
+ RemovePvpTalent(talentInfo);
+ }
+
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ _SaveTalents(trans);
+ _SaveSpells(trans);
+ CharacterDatabase.CommitTransaction(trans);
+}
+
Mail* Player::GetMail(uint32 id)
{
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
@@ -6960,6 +6988,11 @@ void Player::UpdateArea(uint32 newArea)
UpdateAreaDependentAuras(newArea);
UpdateAreaAndZonePhase();
+ if (IsAreaThatActivatesPvpTalents(newArea))
+ EnablePvpRules();
+ else
+ DisablePvpRules();
+
// previously this was in UpdateZone (but after UpdateArea) so nothing will break
pvpInfo.IsInNoPvPArea = false;
if (area && area->IsSanctuary()) // in sanctuary
@@ -7003,7 +7036,7 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
m_zoneUpdateId = newZone;
m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
- // zone changed, so area changed as well, update it
+ // zone changed, so area changed as well, update it.
UpdateArea(newArea);
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(newZone);
@@ -7140,6 +7173,9 @@ void Player::DuelComplete(DuelCompleteType type)
SendMessageToSet(duelWinner.Write(), true);
}
+ duel->opponent->DisablePvpRules();
+ DisablePvpRules();
+
sScriptMgr->OnPlayerDuelEnd(duel->opponent, this, type);
switch (type)
@@ -18001,6 +18037,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
UpdateDisplayPower();
_LoadTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS));
+ _LoadPvpTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PVP_TALENTS));
_LoadSpells(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS));
GetSession()->GetCollectionMgr()->LoadToys();
GetSession()->GetCollectionMgr()->LoadHeirlooms();
@@ -18030,6 +18067,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
InitTalentForLevel();
LearnDefaultSkills();
LearnCustomSpells();
+ if (getLevel() < PLAYER_LEVEL_MIN_HONOR)
+ ResetPvpTalents();
// must be before inventory (some items required reputation check)
m_reputationMgr->LoadFromDB(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION));
@@ -26150,6 +26189,197 @@ void Player::ResetTalentSpecialization()
UpdateItemSetAuras(false);
}
+TalentLearnResult Player::LearnPvpTalent(uint32 talentID, int32* spellOnCooldown)
+{
+ if (IsInCombat())
+ return TALENT_FAILED_AFFECTING_COMBAT;
+
+ if (getLevel() < PLAYER_LEVEL_MIN_HONOR)
+ return TALENT_FAILED_UNKNOWN;
+
+ PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(talentID);
+ if (!talentInfo)
+ return TALENT_FAILED_UNKNOWN;
+
+ if (talentInfo->SpecID)
+ {
+ if (talentInfo->SpecID != GetInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))
+ return TALENT_FAILED_UNKNOWN;
+ }
+ else if (talentInfo->Role >= 0)
+ {
+ if (talentInfo->Role != sChrSpecializationStore.AssertEntry(GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))->Role)
+ return TALENT_FAILED_UNKNOWN;
+ }
+
+ // prevent learn talent for different class (cheating)
+ if (talentInfo->ClassID && talentInfo->ClassID != getClass())
+ return TALENT_FAILED_UNKNOWN;
+
+ if (!GetPrestigeLevel())
+ if (sDB2Manager.GetRequiredHonorLevelForPvpTalent(talentInfo) > GetHonorLevel())
+ return TALENT_FAILED_UNKNOWN;
+
+ // Check if player doesn't have any talent in current tier
+ for (uint32 c = 0; c < MAX_PVP_TALENT_COLUMNS; ++c)
+ {
+ for (PvpTalentEntry const* talent : sDB2Manager.GetPvpTalentsByPosition(getClass(), talentInfo->TierID, c))
+ {
+ if (HasPvpTalent(talent->ID, GetActiveTalentGroup()) && !HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC))
+ return TALENT_FAILED_REST_AREA;
+
+ if (GetSpellHistory()->HasCooldown(talent->SpellID))
+ {
+ *spellOnCooldown = talent->SpellID;
+ return TALENT_FAILED_CANT_REMOVE_TALENT;
+ }
+
+ RemovePvpTalent(talent);
+ }
+ }
+
+ if (!AddPvpTalent(talentInfo, GetActiveTalentGroup(), true))
+ return TALENT_FAILED_UNKNOWN;
+
+ return TALENT_LEARN_OK;
+}
+
+bool Player::AddPvpTalent(PvpTalentEntry const* talent, uint8 activeTalentGroup, bool learning)
+{
+ ASSERT(talent);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID);
+ if (!spellInfo)
+ {
+ TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) does not exist.", talent->SpellID);
+ return false;
+ }
+
+ if (!SpellMgr::IsSpellValid(spellInfo, this, false))
+ {
+ TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: %u) is invalid", talent->SpellID);
+ return false;
+ }
+
+ if (HasPvpRulesEnabled())
+ LearnSpell(talent->SpellID, false);
+
+ // Move this to toggle ?
+ if (talent->OverridesSpellID)
+ AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
+
+ PlayerTalentMap::iterator itr = GetPvpTalentMap(activeTalentGroup)->find(talent->ID);
+ if (itr != GetPvpTalentMap(activeTalentGroup)->end())
+ itr->second = PLAYERSPELL_UNCHANGED;
+ else
+ (*GetPvpTalentMap(activeTalentGroup))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
+
+ return true;
+}
+
+void Player::RemovePvpTalent(PvpTalentEntry const* talent)
+{
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID);
+ if (!spellInfo)
+ return;
+
+ RemoveSpell(talent->SpellID, true);
+
+ // Move this to toggle ?
+ if (talent->OverridesSpellID)
+ RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID);
+
+ // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
+ PlayerTalentMap::iterator plrPvpTalent = GetPvpTalentMap(GetActiveTalentGroup())->find(talent->ID);
+ if (plrPvpTalent != GetPvpTalentMap(GetActiveTalentGroup())->end())
+ plrPvpTalent->second = PLAYERSPELL_REMOVED;
+}
+
+void Player::TogglePvpTalents(bool enable)
+{
+ PlayerTalentMap const* pvpTalents = GetPvpTalentMap(GetActiveTalentGroup());
+ for (PlayerTalentMap::value_type const& v : *pvpTalents)
+ {
+ PvpTalentEntry const* pvpTalentInfo = sPvpTalentStore.AssertEntry(v.first);
+ if (enable && v.second != PLAYERSPELL_REMOVED)
+ LearnSpell(pvpTalentInfo->SpellID, false);
+ else
+ RemoveSpell(pvpTalentInfo->SpellID, true);
+ }
+}
+
+bool Player::HasPvpTalent(uint32 talentID, uint8 activeTalentGroup) const
+{
+ PlayerTalentMap::const_iterator itr = GetPvpTalentMap(activeTalentGroup)->find(talentID);
+ return (itr != GetPvpTalentMap(activeTalentGroup)->end() && itr->second != PLAYERSPELL_REMOVED);
+}
+
+void Player::EnablePvpRules(bool dueToCombat /*= false*/)
+{
+ if (HasPvpRulesEnabled())
+ return;
+
+ if (!HasSpell(195710)) // Honorable Medallion
+ CastSpell(this, 208682); // Learn Gladiator's Medallion
+
+ CastSpell(this, SPELL_PVP_RULES_ENABLED);
+ if (!dueToCombat)
+ {
+ if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
+ {
+ aura->SetMaxDuration(-1);
+ aura->SetDuration(-1);
+ }
+ }
+}
+
+void Player::DisablePvpRules()
+{
+ // Don't disable pvp rules when in pvp zone.
+ if (IsInAreaThatActivatesPvpTalents())
+ return;
+
+ if (!GetCombatTimer())
+ RemoveAurasDueToSpell(SPELL_PVP_RULES_ENABLED);
+ else if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
+ aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
+}
+
+bool Player::HasPvpRulesEnabled() const
+{
+ return HasAura(SPELL_PVP_RULES_ENABLED);
+}
+
+bool Player::IsInAreaThatActivatesPvpTalents() const
+{
+ return IsAreaThatActivatesPvpTalents(GetAreaId());
+}
+
+bool Player::IsAreaThatActivatesPvpTalents(uint32 areaID) const
+{
+ if (InBattleground())
+ return true;
+
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaID))
+ {
+ do
+ {
+ if (area->IsSanctuary())
+ return false;
+
+ if (area->Flags[0] & AREA_FLAG_ARENA)
+ return true;
+
+ if (sBattlefieldMgr->GetBattlefieldToZoneId(area->ID))
+ return true;
+
+ area = sAreaTableStore.LookupEntry(area->ParentAreaID);
+
+ } while (area);
+ }
+
+ return false;
+}
+
void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode)
{
if (m_lastFallTime >= minfo.jump.fallTime || m_lastFallZ <= minfo.pos.GetPositionZ() || opcode == CMSG_MOVE_FALL_LAND)
@@ -26229,10 +26459,12 @@ void Player::SendTalentsInfoData()
continue;
PlayerTalentMap* talents = GetTalentMap(i);
+ PlayerTalentMap* pvpTalents = GetPvpTalentMap(i);
WorldPackets::Talent::TalentGroupInfo groupInfoPkt;
groupInfoPkt.SpecID = spec->ID;
groupInfoPkt.TalentIDs.reserve(talents->size());
+ groupInfoPkt.PvPTalentIDs.reserve(pvpTalents->size());
for (PlayerTalentMap::const_iterator itr = talents->begin(); itr != talents->end(); ++itr)
{
@@ -26261,6 +26493,33 @@ void Player::SendTalentsInfoData()
groupInfoPkt.TalentIDs.push_back(uint16(itr->first));
}
+ for (PlayerTalentMap::const_iterator itr = pvpTalents->begin(); itr != pvpTalents->end(); ++itr)
+ {
+ if (itr->second == PLAYERSPELL_REMOVED)
+ continue;
+
+ PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(itr->first);
+ if (!talentInfo)
+ {
+ TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown pvp talent id: %u",
+ GetName().c_str(), GetGUID().ToString().c_str(), itr->first);
+ continue;
+ }
+
+ if (talentInfo->ClassID && talentInfo->ClassID != getClass())
+ continue;
+
+ SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID);
+ if (!spellEntry)
+ {
+ TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '%s' (%s) has unknown pvp talent spell: %u",
+ GetName().c_str(), GetGUID().ToString().c_str(), talentInfo->SpellID);
+ continue;
+ }
+
+ groupInfoPkt.PvPTalentIDs.push_back(uint16(itr->first));
+ }
+
packet.Info.TalentGroups.push_back(groupInfoPkt);
}
@@ -26536,6 +26795,18 @@ void Player::_LoadTalents(PreparedQueryResult result)
}
}
+void Player::_LoadPvpTalents(PreparedQueryResult result)
+{
+ // "SELECT TalentID, TalentGroup FROM character_pvp_talent WHERE guid = ?"
+ if (result)
+ {
+ do
+ if (PvpTalentEntry const* talent = sPvpTalentStore.LookupEntry((*result)[0].GetUInt32()))
+ AddPvpTalent(talent, (*result)[1].GetUInt8(), false);
+ while (result->NextRow());
+ }
+}
+
void Player::_SaveTalents(SQLTransaction& trans)
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT);
@@ -26562,6 +26833,30 @@ void Player::_SaveTalents(SQLTransaction& trans)
++itr;
}
}
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PVP_TALENT);
+ stmt->setUInt64(0, GetGUID().GetCounter());
+ trans->Append(stmt);
+
+ for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group)
+ {
+ talents = GetPvpTalentMap(group);
+ for (PlayerTalentMap::iterator itr = talents->begin(); itr != talents->end();)
+ {
+ if (itr->second == PLAYERSPELL_REMOVED)
+ {
+ itr = talents->erase(itr);
+ continue;
+ }
+
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_PVP_TALENT);
+ stmt->setUInt64(0, GetGUID().GetCounter());
+ stmt->setUInt32(1, itr->first);
+ stmt->setUInt8(2, group);
+ trans->Append(stmt);
+ ++itr;
+ }
+ }
}
void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec)
@@ -26638,6 +26933,36 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec)
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
}
+ for (uint32 pvpTalentID = 0; pvpTalentID < sPvpTalentStore.GetNumRows(); ++pvpTalentID)
+ {
+ PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(pvpTalentID);
+ if (!talentInfo)
+ continue;
+
+ // unlearn only talents for character class
+ // some spell learned by one class as normal spells or know at creation but another class learn it as talent,
+ // to prevent unexpected lost normal learned spell skip another class talents
+ if (talentInfo->ClassID && talentInfo->ClassID != getClass())
+ continue;
+
+ if (talentInfo->SpellID == 0)
+ continue;
+
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID);
+ if (!spellInfo)
+ continue;
+
+ RemoveSpell(talentInfo->SpellID, true);
+
+ // search for spells that the talent teaches and unlearn them
+ for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE))
+ if (effect && effect->TriggerSpell > 0 && effect->Effect == SPELL_EFFECT_LEARN_SPELL)
+ RemoveSpell(effect->TriggerSpell, true);
+
+ if (talentInfo->OverridesSpellID)
+ RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
+ }
+
// Remove spec specific spells
RemoveSpecializationSpells();
@@ -26671,6 +26996,23 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec)
}
}
+ for (uint32 pvpTalentID = 0; pvpTalentID < sTalentStore.GetNumRows(); ++pvpTalentID)
+ {
+ PvpTalentEntry const* talentInfo = sPvpTalentStore.LookupEntry(pvpTalentID);
+ if (!talentInfo)
+ continue;
+
+ // learn only talents for character class (or x-class talents)
+ if (talentInfo->ClassID && talentInfo->ClassID != getClass())
+ continue;
+
+ if (!talentInfo->SpellID)
+ continue;
+
+ if (HasPvpTalent(talentInfo->ID, GetActiveTalentGroup()))
+ AddPvpTalent(talentInfo, GetActiveTalentGroup(), true);
+ }
+
LearnSpecializationSpells();
if (CanUseMastery())
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 12b19dd4485..63fa4c2398f 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -52,6 +52,7 @@ struct ItemTemplate;
struct Loot;
struct Mail;
struct MapEntry;
+struct PvpTalentEntry;
struct QuestPackageItemEntry;
struct RewardPackEntry;
struct SkillRaceClassInfoEntry;
@@ -814,6 +815,7 @@ enum PlayerLoginQueryIndex
PLAYER_LOGIN_QUERY_LOAD_BG_DATA,
PLAYER_LOGIN_QUERY_LOAD_GLYPHS,
PLAYER_LOGIN_QUERY_LOAD_TALENTS,
+ PLAYER_LOGIN_QUERY_LOAD_PVP_TALENTS,
PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA,
PLAYER_LOGIN_QUERY_LOAD_SKILLS,
PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS,
@@ -1009,6 +1011,7 @@ struct TC_GAME_API SpecializationInfo
}
PlayerTalentMap Talents[MAX_SPECIALIZATIONS];
+ PlayerTalentMap PvpTalents[MAX_SPECIALIZATIONS];
std::vector<uint32> Glyphs[MAX_SPECIALIZATIONS];
uint32 ResetTalentsCost;
time_t ResetTalentsTime;
@@ -1029,8 +1032,9 @@ struct PlayerDynamicFieldSpellModByLabel
};
#pragma pack(pop)
-uint8 const PLAYER_MAX_HONOR_LEVEL = 50;
-uint8 const PLAYER_LEVEL_MIN_HONOR = 110;
+uint8 constexpr PLAYER_MAX_HONOR_LEVEL = 50;
+uint8 constexpr PLAYER_LEVEL_MIN_HONOR = 110;
+uint32 constexpr SPELL_PVP_RULES_ENABLED = 134735;
class TC_GAME_API Player : public Unit, public GridObject<Player>
{
@@ -1632,6 +1636,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 GetDefaultSpecId() const;
bool ResetTalents(bool noCost = false);
+ void ResetPvpTalents();
uint32 GetNextResetTalentsCost() const;
void InitTalentForLevel();
void SendTalentsInfoData();
@@ -1642,11 +1647,24 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 CalculateTalentsTiers() const;
void ResetTalentSpecialization();
+ TalentLearnResult LearnPvpTalent(uint32 talentID, int32* spellOnCooldown);
+ bool AddPvpTalent(PvpTalentEntry const* talent, uint8 activeTalentGroup, bool learning);
+ void RemovePvpTalent(PvpTalentEntry const* talent);
+ void TogglePvpTalents(bool enable);
+ bool HasPvpTalent(uint32 talentID, uint8 activeTalentGroup) const;
+ void EnablePvpRules(bool dueToCombat = false);
+ void DisablePvpRules();
+ bool HasPvpRulesEnabled() const;
+ bool IsInAreaThatActivatesPvpTalents() const;
+ bool IsAreaThatActivatesPvpTalents(uint32 areaID) const;
+
// Dual Spec
void ActivateTalentGroup(ChrSpecializationEntry const* spec);
PlayerTalentMap const* GetTalentMap(uint8 spec) const { return &_specializationInfo.Talents[spec]; }
PlayerTalentMap* GetTalentMap(uint8 spec) { return &_specializationInfo.Talents[spec]; }
+ PlayerTalentMap const* GetPvpTalentMap(uint8 spec) const { return &_specializationInfo.PvpTalents[spec]; }
+ PlayerTalentMap* GetPvpTalentMap(uint8 spec) { return &_specializationInfo.PvpTalents[spec]; }
std::vector<uint32> const& GetGlyphs(uint8 spec) const { return _specializationInfo.Glyphs[spec]; }
std::vector<uint32>& GetGlyphs(uint8 spec) { return _specializationInfo.Glyphs[spec]; }
ActionButtonList const& GetActionButtons() const { return m_actionButtons; }
@@ -2429,6 +2447,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void _LoadBGData(PreparedQueryResult result);
void _LoadGlyphs(PreparedQueryResult result);
void _LoadTalents(PreparedQueryResult result);
+ void _LoadPvpTalents(PreparedQueryResult result);
void _LoadInstanceTimeRestrictions(PreparedQueryResult result);
void _LoadCurrency(PreparedQueryResult result);
void _LoadCUFProfiles(PreparedQueryResult result);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index be1ead476fa..c568480de1e 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -7798,7 +7798,11 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy)
return;
if (PvP)
+ {
m_CombatTimer = 5000;
+ if (Player* me = ToPlayer())
+ me->EnablePvpRules(true);
+ }
if (IsInCombat() || HasUnitState(UNIT_STATE_EVADE))
return;
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index a7342644a46..f2a93781714 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -229,6 +229,10 @@ bool LoginQueryHolder::Initialize()
stmt->setUInt64(0, lowGuid);
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TALENTS, stmt);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_PVP_TALENTS);
+ stmt->setUInt64(0, lowGuid);
+ res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PVP_TALENTS, stmt);
+
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA);
stmt->setUInt64(0, lowGuid);
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA, stmt);
diff --git a/src/server/game/Handlers/DuelHandler.cpp b/src/server/game/Handlers/DuelHandler.cpp
index c7bb5a36d17..41892196367 100644
--- a/src/server/game/Handlers/DuelHandler.cpp
+++ b/src/server/game/Handlers/DuelHandler.cpp
@@ -77,6 +77,8 @@ void WorldSession::HandleDuelAccepted()
WorldPacket const* worldPacket = packet.Write();
player->GetSession()->SendPacket(worldPacket);
plTarget->GetSession()->SendPacket(worldPacket);
+ player->EnablePvpRules();
+ plTarget->EnablePvpRules();
}
void WorldSession::HandleDuelCancelled()
diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp
index f802c651f60..fc1741c6885 100644
--- a/src/server/game/Handlers/SkillHandler.cpp
+++ b/src/server/game/Handlers/SkillHandler.cpp
@@ -51,6 +51,30 @@ void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents&
_player->SendTalentsInfoData();
}
+void WorldSession::HandleLearnPvpTalentsOpcode(WorldPackets::Talent::LearnPvpTalents& packet)
+{
+ WorldPackets::Talent::LearnPvpTalentsFailed learnPvpTalentsFailed;
+ bool anythingLearned = false;
+ for (uint32 talentId : packet.Talents)
+ {
+ if (TalentLearnResult result = _player->LearnPvpTalent(talentId, &learnPvpTalentsFailed.SpellID))
+ {
+ if (!learnPvpTalentsFailed.Reason)
+ learnPvpTalentsFailed.Reason = result;
+
+ learnPvpTalentsFailed.Talents.push_back(talentId);
+ }
+ else
+ anythingLearned = true;
+ }
+
+ if (learnPvpTalentsFailed.Reason)
+ SendPacket(learnPvpTalentsFailed.Write());
+
+ if (anythingLearned)
+ _player->SendTalentsInfoData();
+}
+
void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe)
{
Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(confirmRespecWipe.RespecMaster, UNIT_NPC_FLAG_TRAINER);
diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp
index 9c90a12ca3e..c5cbb0769a5 100644
--- a/src/server/game/Server/Packets/TalentPackets.cpp
+++ b/src/server/game/Server/Packets/TalentPackets.cpp
@@ -89,3 +89,21 @@ WorldPacket const* WorldPackets::Talent::ActiveGlyphs::Write()
return &_worldPacket;
}
+
+void WorldPackets::Talent::LearnPvpTalents::Read()
+{
+ Talents.resize(_worldPacket.ReadBits(6));
+ for (uint32 i = 0; i < Talents.size(); ++i)
+ _worldPacket >> Talents[i];
+}
+
+WorldPacket const* WorldPackets::Talent::LearnPvpTalentsFailed::Write()
+{
+ _worldPacket.WriteBits(Reason, 4);
+ _worldPacket << int32(SpellID);
+ _worldPacket << uint32(Talents.size());
+ if (!Talents.empty())
+ _worldPacket.append(Talents.data(), Talents.size());
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h
index a61a4d78d89..920f724095e 100644
--- a/src/server/game/Server/Packets/TalentPackets.h
+++ b/src/server/game/Server/Packets/TalentPackets.h
@@ -113,6 +113,28 @@ namespace WorldPackets
std::vector<GlyphBinding> Glyphs;
bool IsFullUpdate = false;
};
+
+ class LearnPvpTalents final : public ClientPacket
+ {
+ public:
+ LearnPvpTalents(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_PVP_TALENTS, std::move(packet)) { }
+
+ void Read() override;
+
+ Array<uint16, 6> Talents;
+ };
+
+ class LearnPvpTalentsFailed final : public ServerPacket
+ {
+ public:
+ LearnPvpTalentsFailed() : ServerPacket(SMSG_LEARN_PVP_TALENTS_FAILED, 1 + 4 + 4 + 2 * MAX_PVP_TALENT_TIERS) { }
+
+ WorldPacket const* Write() override;
+
+ uint32 Reason = 0;
+ int32 SpellID = 0;
+ std::vector<uint16> Talents;
+ };
}
}
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 28bd8c80701..19b24370285 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -478,7 +478,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_JOIN_RATED_BATTLEGROUND, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_KEEP_ALIVE, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess);
DEFINE_HANDLER(CMSG_KEYBOUND_OVERRIDE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_LEARN_PVP_TALENTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_LEARN_PVP_TALENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnPvpTalentsOpcode);
DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentsOpcode);
DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveGroupOpcode);
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
@@ -1316,8 +1316,8 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_ITEM_UPGRADE_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_KICK_REASON, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARNED_SPELLS, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_PVP_TALENTS_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_TALENTS_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_PVP_TALENTS_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_TALENTS_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEVEL_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEVEL_UP_INFO, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LFG_BOOT_PLAYER, STATUS_NEVER, CONNECTION_TYPE_REALM);
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 8d8f00483c7..9de54534f0e 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -665,6 +665,7 @@ namespace WorldPackets
namespace Talent
{
class LearnTalents;
+ class LearnPvpTalents;
class ConfirmRespecWipe;
}
@@ -1426,6 +1427,7 @@ class TC_GAME_API WorldSession
void HandleMissileTrajectoryCollision(WorldPackets::Spells::MissileTrajectoryCollision& packet);
void HandleUpdateMissileTrajectory(WorldPackets::Spells::UpdateMissileTrajectory& packet);
+ void HandleLearnPvpTalentsOpcode(WorldPackets::Talent::LearnPvpTalents& packet);
void HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet);
void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe);
void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet);
diff --git a/src/server/game/Spells/Auras/SpellAuraDefines.h b/src/server/game/Spells/Auras/SpellAuraDefines.h
index 893c7a903d1..4e33349d33d 100644
--- a/src/server/game/Spells/Auras/SpellAuraDefines.h
+++ b/src/server/game/Spells/Auras/SpellAuraDefines.h
@@ -188,7 +188,7 @@ enum AuraType : uint32
SPELL_AURA_MOD_REGEN_DURING_COMBAT = 116,
SPELL_AURA_MOD_MECHANIC_RESISTANCE = 117,
SPELL_AURA_MOD_HEALING_PCT = 118,
- SPELL_AURA_119 = 119, // old SPELL_AURA_SHARE_PET_TRACKING
+ SPELL_AURA_PVP_TALENTS = 119,
SPELL_AURA_UNTRACKABLE = 120,
SPELL_AURA_EMPATHY = 121,
SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT = 122,
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index 7933ea5781f..01bf86a3421 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -184,7 +184,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNoImmediateEffect, //116 SPELL_AURA_MOD_REGEN_DURING_COMBAT
&AuraEffect::HandleNoImmediateEffect, //117 SPELL_AURA_MOD_MECHANIC_RESISTANCE implemented in Unit::MagicSpellHitResult
&AuraEffect::HandleNoImmediateEffect, //118 SPELL_AURA_MOD_HEALING_PCT implemented in Unit::SpellHealingBonus
- &AuraEffect::HandleUnused, //119 unused (4.3.4) old SPELL_AURA_SHARE_PET_TRACKING
+ &AuraEffect::HandleAuraPvpTalents, //119 SPELL_AURA_PVP_TALENTS
&AuraEffect::HandleAuraUntrackable, //120 SPELL_AURA_UNTRACKABLE
&AuraEffect::HandleAuraEmpathy, //121 SPELL_AURA_EMPATHY
&AuraEffect::HandleModOffhandDamagePercent, //122 SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT
@@ -6147,3 +6147,17 @@ void AuraEffect::HandleCreateAreaTrigger(AuraApplication const* aurApp, uint8 mo
caster->RemoveAreaTrigger(this);
}
}
+
+void AuraEffect::HandleAuraPvpTalents(AuraApplication const* auraApp, uint8 mode, bool apply) const
+{
+ if (!(mode & AURA_EFFECT_HANDLE_REAL))
+ return;
+
+ if (Player* target = auraApp->GetTarget()->ToPlayer())
+ {
+ if (apply)
+ target->TogglePvpTalents(true);
+ else if (!target->HasAuraType(SPELL_AURA_PVP_TALENTS))
+ target->TogglePvpTalents(false);
+ }
+}
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h
index be3e81b9707..7493aaab17a 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.h
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.h
@@ -330,6 +330,9 @@ class TC_GAME_API AuraEffect
void HandleProcTriggerSpellAuraProc(AuraApplication* aurApp, ProcEventInfo& eventInfo);
void HandleProcTriggerSpellWithValueAuraProc(AuraApplication* aurApp, ProcEventInfo& eventInfo);
void HandleProcTriggerDamageAuraProc(AuraApplication* aurApp, ProcEventInfo& eventInfo);
+
+ // pvp talents
+ void HandleAuraPvpTalents(AuraApplication const* auraApp, uint8 mode, bool apply) const;
};
namespace Trinity