diff options
author | Jeremy <Golrag@users.noreply.github.com> | 2018-03-04 22:32:08 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2018-03-04 22:32:08 +0100 |
commit | f411b7a90e4900c9e2df7c1e0c7a301360408cec (patch) | |
tree | 9793744cf0749d37bb13d6d69355b8672e5f6c34 | |
parent | e0eafc1cac9d840f897f9269e8440aaebfab6a96 (diff) |
Core/Player: Implement PvP Talents (#19962)
* SPELL_AURA_119 -> SPELL_AURA_PVP_TALENTS
* Learn Honorable Medallion when pvp rules are enabled
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 |