diff options
author | Shauren <shauren.trinity@gmail.com> | 2022-12-16 22:44:55 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-12-16 22:44:55 +0100 |
commit | 0cc5ab8372f19dad7412038d52dcd39db5e0e171 (patch) | |
tree | 99e621862e90fc9f073897445d4a8bdd4fbfa557 | |
parent | 9be60f240960f6538329b5e017f435c6237a89ea (diff) |
Core/Players: Implemented new talent system
31 files changed, 3688 insertions, 71 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 5e9ebc0b9c5..53120ed9e3b 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -458,10 +458,11 @@ DROP TABLE IF EXISTS `character_action`; CREATE TABLE `character_action` ( `guid` bigint unsigned NOT NULL DEFAULT '0', `spec` tinyint unsigned NOT NULL DEFAULT '0', + `traitConfigId` int NOT NULL DEFAULT '0', `button` tinyint unsigned NOT NULL DEFAULT '0', `action` bigint unsigned NOT NULL DEFAULT '0', `type` tinyint unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`guid`,`spec`,`button`) + PRIMARY KEY (`guid`,`spec`,`traitConfigId`,`button`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -1700,6 +1701,63 @@ LOCK TABLES `character_talent` WRITE; UNLOCK TABLES; -- +-- Table structure for table `character_trait_config` +-- + +DROP TABLE IF EXISTS `character_trait_config`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `character_trait_config` ( + `guid` bigint unsigned NOT NULL, + `traitConfigId` int NOT NULL, + `type` int NOT NULL, + `chrSpecializationId` int DEFAULT NULL, + `combatConfigFlags` int DEFAULT NULL, + `localIdentifier` int DEFAULT NULL, + `skillLineId` int DEFAULT NULL, + `traitSystemId` int DEFAULT NULL, + `name` varchar(260) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + PRIMARY KEY (`guid`,`traitConfigId`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_trait_config` +-- + +LOCK TABLES `character_trait_config` WRITE; +/*!40000 ALTER TABLE `character_trait_config` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_trait_config` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `character_trait_entry` +-- + +DROP TABLE IF EXISTS `character_trait_entry`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `character_trait_entry` ( + `guid` bigint unsigned NOT NULL, + `traitConfigId` int NOT NULL, + `traitNodeId` int NOT NULL, + `traitNodeEntryId` int NOT NULL, + `rank` int NOT NULL DEFAULT '0', + `grantedRanks` int NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`traitConfigId`,`traitNodeId`,`traitNodeEntryId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_trait_entry` +-- + +LOCK TABLES `character_trait_entry` WRITE; +/*!40000 ALTER TABLE `character_trait_entry` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_trait_entry` ENABLE KEYS */; +UNLOCK TABLES; + +-- -- Table structure for table `character_transmog_outfits` -- @@ -3606,7 +3664,8 @@ INSERT INTO `updates` VALUES ('2022_10_03_00_characters.sql','7B062787230D9158A622EB4AFE7FA6D18AB47BB3','ARCHIVED','2022-10-03 22:32:58',0), ('2022_10_03_01_characters.sql','7CF58BD9CC366301CC992017028568C8774C4BC2','ARCHIVED','2022-10-03 22:36:38',0), ('2022_10_03_02_characters.sql','33135AB3132943F15F4849A16EC5EFEA402F24F6','ARCHIVED','2022-10-03 22:38:27',0), -('2022_11_20_00_characters.sql','4EB8BB24CAF16B0962DF3EF92C77BE05E234CFA6','ARCHIVED','2022-11-20 11:05:20',0); +('2022_11_20_00_characters.sql','4EB8BB24CAF16B0962DF3EF92C77BE05E234CFA6','ARCHIVED','2022-11-20 11:05:20',0), +('2022_12_16_00_characters.sql','ABD1E101FE6629E0520C91E98942E55067EDD492','RELEASED','2022-12-16 22:52:19',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2022_12_16_00_characters.sql b/sql/updates/characters/master/2022_12_16_00_characters.sql index 8a0dd2989cd..654062fc6ca 100644 --- a/sql/updates/characters/master/2022_12_16_00_characters.sql +++ b/sql/updates/characters/master/2022_12_16_00_characters.sql @@ -8,3 +8,40 @@ ALTER TABLE `character_inventory` ADD UNIQUE KEY `uk_location` (`guid`,`bag`,`sl ALTER TABLE `character_inventory` DROP `newSlot`;
UPDATE `characters` SET `equipmentCache`=CONCAT(`equipmentCache`, '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ');
+
+--
+-- Table structure for table `character_trait_entry`
+--
+DROP TABLE IF EXISTS `character_trait_entry`;
+CREATE TABLE `character_trait_entry` (
+ `guid` bigint unsigned NOT NULL,
+ `traitConfigId` int NOT NULL,
+ `traitNodeId` int NOT NULL,
+ `traitNodeEntryId` int NOT NULL,
+ `rank` int NOT NULL DEFAULT '0',
+ `grantedRanks` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`guid`,`traitConfigId`,`traitNodeId`,`traitNodeEntryId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `character_trait_config`
+--
+DROP TABLE IF EXISTS `character_trait_config`;
+CREATE TABLE `character_trait_config` (
+ `guid` bigint unsigned NOT NULL,
+ `traitConfigId` int NOT NULL,
+ `type` int NOT NULL,
+ `chrSpecializationId` int DEFAULT NULL,
+ `combatConfigFlags` int DEFAULT NULL,
+ `localIdentifier` int DEFAULT NULL,
+ `skillLineId` int DEFAULT NULL,
+ `traitSystemId` int DEFAULT NULL,
+ `name` varchar(260) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ PRIMARY KEY (`guid`,`traitConfigId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+ALTER TABLE `character_action` ADD `traitConfigId` int NOT NULL DEFAULT 0 AFTER `spec`;
+ALTER TABLE `character_action` DROP PRIMARY KEY;
+ALTER TABLE `character_action` ADD PRIMARY KEY (`guid`,`spec`,`traitConfigId`,`button`);
+
+DELETE FROM `character_talent`;
diff --git a/sql/updates/hotfixes/master/2022_12_16_01_hotfixes.sql b/sql/updates/hotfixes/master/2022_12_16_01_hotfixes.sql new file mode 100644 index 00000000000..a76ff3ea1a1 --- /dev/null +++ b/sql/updates/hotfixes/master/2022_12_16_01_hotfixes.sql @@ -0,0 +1,382 @@ +-- +-- Table structure for table `skill_line_x_trait_tree` +-- +DROP TABLE IF EXISTS `skill_line_x_trait_tree`; +CREATE TABLE `skill_line_x_trait_tree` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `SkillLineID` int(11) NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `OrderIndex` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_cond` +-- +DROP TABLE IF EXISTS `trait_cond`; +CREATE TABLE `trait_cond` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `CondType` int(11) NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `GrantedRanks` int(11) NOT NULL DEFAULT '0', + `QuestID` int(11) NOT NULL DEFAULT '0', + `AchievementID` int(11) NOT NULL DEFAULT '0', + `SpecSetID` int(11) NOT NULL DEFAULT '0', + `TraitNodeGroupID` int(11) NOT NULL DEFAULT '0', + `TraitNodeID` int(11) NOT NULL DEFAULT '0', + `TraitCurrencyID` int(11) NOT NULL DEFAULT '0', + `SpentAmountRequired` int(11) NOT NULL DEFAULT '0', + `Flags` int(11) NOT NULL DEFAULT '0', + `RequiredLevel` int(11) NOT NULL DEFAULT '0', + `FreeSharedStringID` int(11) NOT NULL DEFAULT '0', + `SpendMoreSharedStringID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_cost` +-- +DROP TABLE IF EXISTS `trait_cost`; +CREATE TABLE `trait_cost` ( + `InternalName` text, + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `Amount` int(11) NOT NULL DEFAULT '0', + `TraitCurrencyID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_currency` +-- +DROP TABLE IF EXISTS `trait_currency`; +CREATE TABLE `trait_currency` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `Type` int(11) NOT NULL DEFAULT '0', + `CurrencyTypesID` int(11) NOT NULL DEFAULT '0', + `Flags` int(11) NOT NULL DEFAULT '0', + `Icon` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_currency_source` +-- +DROP TABLE IF EXISTS `trait_currency_source`; +CREATE TABLE `trait_currency_source` ( + `Requirement` text, + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCurrencyID` int(11) NOT NULL DEFAULT '0', + `Amount` int(11) NOT NULL DEFAULT '0', + `QuestID` int(11) NOT NULL DEFAULT '0', + `AchievementID` int(11) NOT NULL DEFAULT '0', + `PlayerLevel` int(11) NOT NULL DEFAULT '0', + `TraitNodeEntryID` int(11) NOT NULL DEFAULT '0', + `OrderIndex` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_currency_source_locale` +-- +DROP TABLE IF EXISTS `trait_currency_source_locale`; +CREATE TABLE `trait_currency_source_locale` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Requirement_lang` text, + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); + +-- +-- Table structure for table `trait_definition` +-- +DROP TABLE IF EXISTS `trait_definition`; +CREATE TABLE `trait_definition` ( + `OverrideName` text, + `OverrideSubtext` text, + `OverrideDescription` text, + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `SpellID` int(11) NOT NULL DEFAULT '0', + `OverrideIcon` int(11) NOT NULL DEFAULT '0', + `OverridesSpellID` int(11) NOT NULL DEFAULT '0', + `VisibleSpellID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_definition_locale` +-- +DROP TABLE IF EXISTS `trait_definition_locale`; +CREATE TABLE `trait_definition_locale` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `OverrideName_lang` text, + `OverrideSubtext_lang` text, + `OverrideDescription_lang` text, + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci +PARTITION BY LIST COLUMNS(locale) +(PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); + +-- +-- Table structure for table `trait_definition_effect_points` +-- +DROP TABLE IF EXISTS `trait_definition_effect_points`; +CREATE TABLE `trait_definition_effect_points` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitDefinitionID` int(11) NOT NULL DEFAULT '0', + `EffectIndex` int(11) NOT NULL DEFAULT '0', + `OperationType` int(11) NOT NULL DEFAULT '0', + `CurveID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_edge` +-- +DROP TABLE IF EXISTS `trait_edge`; +CREATE TABLE `trait_edge` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `VisualStyle` int(11) NOT NULL DEFAULT '0', + `LeftTraitNodeID` int(11) NOT NULL DEFAULT '0', + `RightTraitNodeID` int(11) NOT NULL DEFAULT '0', + `Type` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node` +-- +DROP TABLE IF EXISTS `trait_node`; +CREATE TABLE `trait_node` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `PosX` int(11) NOT NULL DEFAULT '0', + `PosY` int(11) NOT NULL DEFAULT '0', + `Type` tinyint(4) NOT NULL DEFAULT '0', + `Flags` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_entry` +-- +DROP TABLE IF EXISTS `trait_node_entry`; +CREATE TABLE `trait_node_entry` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitDefinitionID` int(11) NOT NULL DEFAULT '0', + `MaxRanks` int(11) NOT NULL DEFAULT '0', + `NodeEntryType` tinyint(3) unsigned NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_entry_x_trait_cond` +-- +DROP TABLE IF EXISTS `trait_node_entry_x_trait_cond`; +CREATE TABLE `trait_node_entry_x_trait_cond` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCondID` int(11) NOT NULL DEFAULT '0', + `TraitNodeEntryID` int(10) unsigned NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_entry_x_trait_cost` +-- +DROP TABLE IF EXISTS `trait_node_entry_x_trait_cost`; +CREATE TABLE `trait_node_entry_x_trait_cost` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitNodeEntryID` int(11) NOT NULL DEFAULT '0', + `TraitCostID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_group` +-- +DROP TABLE IF EXISTS `trait_node_group`; +CREATE TABLE `trait_node_group` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `Flags` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_group_x_trait_cond` +-- +DROP TABLE IF EXISTS `trait_node_group_x_trait_cond`; +CREATE TABLE `trait_node_group_x_trait_cond` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCondID` int(11) NOT NULL DEFAULT '0', + `TraitNodeGroupID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_group_x_trait_cost` +-- +DROP TABLE IF EXISTS `trait_node_group_x_trait_cost`; +CREATE TABLE `trait_node_group_x_trait_cost` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitNodeGroupID` int(11) NOT NULL DEFAULT '0', + `TraitCostID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_group_x_trait_node` +-- +DROP TABLE IF EXISTS `trait_node_group_x_trait_node`; +CREATE TABLE `trait_node_group_x_trait_node` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitNodeGroupID` int(11) NOT NULL DEFAULT '0', + `TraitNodeID` int(11) NOT NULL DEFAULT '0', + `Index` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_x_trait_cond` +-- +DROP TABLE IF EXISTS `trait_node_x_trait_cond`; +CREATE TABLE `trait_node_x_trait_cond` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCondID` int(11) NOT NULL DEFAULT '0', + `TraitNodeID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_x_trait_cost` +-- +DROP TABLE IF EXISTS `trait_node_x_trait_cost`; +CREATE TABLE `trait_node_x_trait_cost` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitNodeID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCostID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_node_x_trait_node_entry` +-- +DROP TABLE IF EXISTS `trait_node_x_trait_node_entry`; +CREATE TABLE `trait_node_x_trait_node_entry` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitNodeID` int(11) NOT NULL DEFAULT '0', + `TraitNodeEntryID` int(11) NOT NULL DEFAULT '0', + `Index` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_tree` +-- +DROP TABLE IF EXISTS `trait_tree`; +CREATE TABLE `trait_tree` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitSystemID` int(11) NOT NULL DEFAULT '0', + `Unused1000_1` int(11) NOT NULL DEFAULT '0', + `FirstTraitNodeID` int(11) NOT NULL DEFAULT '0', + `PlayerConditionID` int(11) NOT NULL DEFAULT '0', + `Flags` int(11) NOT NULL DEFAULT '0', + `Unused1000_2` float NOT NULL DEFAULT '0', + `Unused1000_3` float NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_tree_loadout` +-- +DROP TABLE IF EXISTS `trait_tree_loadout`; +CREATE TABLE `trait_tree_loadout` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `ChrSpecializationID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +-- +-- Table structure for table `trait_tree_loadout_entry` +-- +DROP TABLE IF EXISTS `trait_tree_loadout_entry`; +CREATE TABLE `trait_tree_loadout_entry` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitTreeLoadoutID` int(11) NOT NULL DEFAULT '0', + `SelectedTraitNodeID` int(11) NOT NULL DEFAULT '0', + `SelectedTraitNodeEntryID` int(11) NOT NULL DEFAULT '0', + `NumPoints` int(11) NOT NULL DEFAULT '0', + `OrderIndex` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_tree_x_trait_cost` +-- +DROP TABLE IF EXISTS `trait_tree_x_trait_cost`; +CREATE TABLE `trait_tree_x_trait_cost` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitTreeID` int(10) unsigned NOT NULL DEFAULT '0', + `TraitCostID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `trait_tree_x_trait_currency` +-- +DROP TABLE IF EXISTS `trait_tree_x_trait_currency`; +CREATE TABLE `trait_tree_x_trait_currency` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `Index` int(11) NOT NULL DEFAULT '0', + `TraitTreeID` int(11) NOT NULL DEFAULT '0', + `TraitCurrencyID` int(11) NOT NULL DEFAULT '0', + `VerifiedBuild` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 5d448f35a68..b3032c7a795 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -119,7 +119,6 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHARACTER_REPUTATION, "SELECT faction, standing, flags FROM character_reputation WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_INVENTORY, "SELECT " SelectItemInstanceContent ", bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE ci.guid = ? ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS, "SELECT a.button, a.action, a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activeTalentGroup AND a.guid = ? ORDER BY button", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_MAIL_COUNT, "SELECT COUNT(*) FROM mail WHERE receiver = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SOCIALLIST, "SELECT cs.friend, c.account, cs.flags, cs.note FROM character_social cs JOIN characters c ON c.guid = cs.friend WHERE cs.guid = ? AND c.deleteinfos_name IS NULL LIMIT 255", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ, orientation FROM character_homebind WHERE guid = ?", CONNECTION_ASYNC); @@ -151,7 +150,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR, "DELETE FROM character_favorite_auctions WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? AND traitConfigId = ? ORDER BY button", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT " SelectItemInstanceContent ", ii.owner_guid, m.id FROM mail_items mi INNER JOIN mail m ON mi.mail_id = m.id LEFT JOIN item_instance ii ON mi.item_guid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE m.receiver = ?", CONNECTION_BOTH); PrepareStatement(CHAR_SEL_MAILITEMS_ARTIFACT, "SELECT a.itemGuid, a.xp, a.artifactAppearanceId, a.artifactTierId, ap.artifactPowerId, ap.purchasedRank FROM item_instance_artifact_powers ap LEFT JOIN item_instance_artifact a ON ap.itemGuid = a.itemGuid INNER JOIN mail_items mi ON a.itemGuid = mi.item_guid INNER JOIN mail m ON mi.mail_id = m.id WHERE m.receiver = ?", CONNECTION_BOTH); PrepareStatement(CHAR_SEL_MAILITEMS_AZERITE, "SELECT iz.itemGuid, iz.xp, iz.level, iz.knowledgeLevel, " @@ -616,9 +615,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() 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); - PrepareStatement(CHAR_UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? and button = ? and spec = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, traitConfigId, button, action, type) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ? AND traitConfigId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? and button = ? and spec = ? AND traitConfigId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG, "DELETE FROM character_action WHERE guid = ? AND traitConfigId = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM, "DELETE FROM character_inventory WHERE item = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT, "DELETE FROM character_inventory WHERE bag = ? AND slot = ? AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_MAIL, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?", CONNECTION_ASYNC); @@ -650,6 +650,14 @@ void CharacterDatabaseConnection::DoPrepareStatements() 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); + PrepareStatement(CHAR_SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks FROM character_trait_entry WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES, "DELETE FROM character_trait_entry WHERE guid = ? AND traitConfigId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, "DELETE FROM character_trait_entry WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHAR_TRAIT_CONFIGS, "SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_TRAIT_CONFIGS, "INSERT INTO character_trait_config (guid, traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS, "DELETE FROM character_trait_config WHERE guid = ? AND traitConfigId = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR, "DELETE FROM character_trait_config WHERE guid = ?", CONNECTION_ASYNC); // Void Storage PrepareStatement(CHAR_SEL_CHAR_VOID_STORAGE, "SELECT itemId, itemEntry, slot, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index c0579fe6a3d..5c19d4780ea 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -91,7 +91,6 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHARACTER_REPUTATION, CHAR_SEL_CHARACTER_INVENTORY, - CHAR_SEL_CHARACTER_ACTIONS, CHAR_SEL_CHARACTER_ACTIONS_SPEC, CHAR_SEL_MAIL_COUNT, CHAR_SEL_CHARACTER_SOCIALLIST, @@ -503,6 +502,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_INS_CHAR_ACTION, CHAR_UPD_CHAR_ACTION, CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC, + CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG, CHAR_DEL_CHAR_INVENTORY_BY_ITEM, CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT, CHAR_UPD_MAIL, @@ -532,6 +532,14 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_CHAR_LIST_SLOT, CHAR_INS_CHAR_FISHINGSTEPS, CHAR_DEL_CHAR_FISHINGSTEPS, + CHAR_SEL_CHAR_TRAIT_ENTRIES, + CHAR_INS_CHAR_TRAIT_ENTRIES, + CHAR_DEL_CHAR_TRAIT_ENTRIES, + CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, + CHAR_SEL_CHAR_TRAIT_CONFIGS, + CHAR_INS_CHAR_TRAIT_CONFIGS, + CHAR_DEL_CHAR_TRAIT_CONFIGS, + CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR, CHAR_SEL_CHAR_VOID_STORAGE, CHAR_REP_CHAR_VOID_STORAGE_ITEM, diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp index 88b8ad18193..f6e3f0ec08f 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.cpp +++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp @@ -1346,6 +1346,11 @@ void HotfixDatabaseConnection::DoPrepareStatements() PREPARE_LOCALE_STMT(HOTFIX_SEL_SKILL_LINE_ABILITY, "SELECT ID, AbilityVerb_lang, AbilityAllVerb_lang FROM skill_line_ability_locale" " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + // SkillLineXTraitTree.db2 + PrepareStatement(HOTFIX_SEL_SKILL_LINE_X_TRAIT_TREE, "SELECT ID, SkillLineID, TraitTreeID, OrderIndex FROM skill_line_x_trait_tree" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_SKILL_LINE_X_TRAIT_TREE, "SELECT MAX(ID) + 1 FROM skill_line_x_trait_tree", CONNECTION_SYNCH); + // SkillRaceClassInfo.db2 PrepareStatement(HOTFIX_SEL_SKILL_RACE_CLASS_INFO, "SELECT ID, RaceMask, SkillID, ClassMask, Flags, Availability, MinLevel, SkillTierID" " FROM skill_race_class_info WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); @@ -1640,6 +1645,122 @@ void HotfixDatabaseConnection::DoPrepareStatements() PrepareStatement(HOTFIX_SEL_TRANSMOG_HOLIDAY, "SELECT ID, RequiredTransmogHoliday FROM transmog_holiday WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRANSMOG_HOLIDAY, "SELECT MAX(ID) + 1 FROM transmog_holiday", CONNECTION_SYNCH); + // TraitCond.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_COND, "SELECT ID, CondType, TraitTreeID, GrantedRanks, QuestID, AchievementID, SpecSetID, TraitNodeGroupID, " + "TraitNodeID, TraitCurrencyID, SpentAmountRequired, Flags, RequiredLevel, FreeSharedStringID, SpendMoreSharedStringID FROM trait_cond" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_COND, "SELECT MAX(ID) + 1 FROM trait_cond", CONNECTION_SYNCH); + + // TraitCost.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_COST, "SELECT InternalName, ID, Amount, TraitCurrencyID FROM trait_cost WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_cost", CONNECTION_SYNCH); + + // TraitCurrency.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_CURRENCY, "SELECT ID, Type, CurrencyTypesID, Flags, Icon FROM trait_currency WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_CURRENCY, "SELECT MAX(ID) + 1 FROM trait_currency", CONNECTION_SYNCH); + + // TraitCurrencySource.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_CURRENCY_SOURCE, "SELECT Requirement, ID, TraitCurrencyID, Amount, QuestID, AchievementID, PlayerLevel, " + "TraitNodeEntryID, OrderIndex FROM trait_currency_source WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_CURRENCY_SOURCE, "SELECT MAX(ID) + 1 FROM trait_currency_source", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRAIT_CURRENCY_SOURCE, "SELECT ID, Requirement_lang FROM trait_currency_source_locale" + " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + + // TraitDefinition.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_DEFINITION, "SELECT OverrideName, OverrideSubtext, OverrideDescription, ID, SpellID, OverrideIcon, " + "OverridesSpellID, VisibleSpellID FROM trait_definition WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_DEFINITION, "SELECT MAX(ID) + 1 FROM trait_definition", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_TRAIT_DEFINITION, "SELECT ID, OverrideName_lang, OverrideSubtext_lang, OverrideDescription_lang" + " FROM trait_definition_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + + // TraitDefinitionEffectPoints.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_DEFINITION_EFFECT_POINTS, "SELECT ID, TraitDefinitionID, EffectIndex, OperationType, CurveID" + " FROM trait_definition_effect_points WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_DEFINITION_EFFECT_POINTS, "SELECT MAX(ID) + 1 FROM trait_definition_effect_points", CONNECTION_SYNCH); + + // TraitEdge.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_EDGE, "SELECT ID, VisualStyle, LeftTraitNodeID, RightTraitNodeID, Type FROM trait_edge" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_EDGE, "SELECT MAX(ID) + 1 FROM trait_edge", CONNECTION_SYNCH); + + // TraitNode.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE, "SELECT ID, TraitTreeID, PosX, PosY, Type, Flags FROM trait_node WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE, "SELECT MAX(ID) + 1 FROM trait_node", CONNECTION_SYNCH); + + // TraitNodeEntry.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_ENTRY, "SELECT ID, TraitDefinitionID, MaxRanks, NodeEntryType FROM trait_node_entry" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_ENTRY, "SELECT MAX(ID) + 1 FROM trait_node_entry", CONNECTION_SYNCH); + + // TraitNodeEntryXTraitCond.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND, "SELECT ID, TraitCondID, TraitNodeEntryID FROM trait_node_entry_x_trait_cond" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND, "SELECT MAX(ID) + 1 FROM trait_node_entry_x_trait_cond", CONNECTION_SYNCH); + + // TraitNodeEntryXTraitCost.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST, "SELECT ID, TraitNodeEntryID, TraitCostID FROM trait_node_entry_x_trait_cost" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_node_entry_x_trait_cost", CONNECTION_SYNCH); + + // TraitNodeGroup.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_GROUP, "SELECT ID, TraitTreeID, Flags FROM trait_node_group WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_GROUP, "SELECT MAX(ID) + 1 FROM trait_node_group", CONNECTION_SYNCH); + + // TraitNodeGroupXTraitCond.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COND, "SELECT ID, TraitCondID, TraitNodeGroupID FROM trait_node_group_x_trait_cond" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COND, "SELECT MAX(ID) + 1 FROM trait_node_group_x_trait_cond", CONNECTION_SYNCH); + + // TraitNodeGroupXTraitCost.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COST, "SELECT ID, TraitNodeGroupID, TraitCostID FROM trait_node_group_x_trait_cost" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_node_group_x_trait_cost", CONNECTION_SYNCH); + + // TraitNodeGroupXTraitNode.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE, "SELECT ID, TraitNodeGroupID, TraitNodeID, `Index` FROM trait_node_group_x_trait_node" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE, "SELECT MAX(ID) + 1 FROM trait_node_group_x_trait_node", CONNECTION_SYNCH); + + // TraitNodeXTraitCond.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COND, "SELECT ID, TraitCondID, TraitNodeID FROM trait_node_x_trait_cond" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COND, "SELECT MAX(ID) + 1 FROM trait_node_x_trait_cond", CONNECTION_SYNCH); + + // TraitNodeXTraitCost.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COST, "SELECT ID, TraitNodeID, TraitCostID FROM trait_node_x_trait_cost" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_node_x_trait_cost", CONNECTION_SYNCH); + + // TraitNodeXTraitNodeEntry.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, "SELECT ID, TraitNodeID, TraitNodeEntryID, `Index` FROM trait_node_x_trait_node_entry" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, "SELECT MAX(ID) + 1 FROM trait_node_x_trait_node_entry", CONNECTION_SYNCH); + + // TraitTree.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_TREE, "SELECT ID, TraitSystemID, Unused1000_1, FirstTraitNodeID, PlayerConditionID, Flags, Unused1000_2, " + "Unused1000_3 FROM trait_tree WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_TREE, "SELECT MAX(ID) + 1 FROM trait_tree", CONNECTION_SYNCH); + + // TraitTreeLoadout.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_TREE_LOADOUT, "SELECT ID, TraitTreeID, ChrSpecializationID FROM trait_tree_loadout" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_TREE_LOADOUT, "SELECT MAX(ID) + 1 FROM trait_tree_loadout", CONNECTION_SYNCH); + + // TraitTreeLoadoutEntry.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_TREE_LOADOUT_ENTRY, "SELECT ID, TraitTreeLoadoutID, SelectedTraitNodeID, SelectedTraitNodeEntryID, NumPoints, " + "OrderIndex FROM trait_tree_loadout_entry WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_TREE_LOADOUT_ENTRY, "SELECT MAX(ID) + 1 FROM trait_tree_loadout_entry", CONNECTION_SYNCH); + + // TraitTreeXTraitCost.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_TREE_X_TRAIT_COST, "SELECT ID, TraitTreeID, TraitCostID FROM trait_tree_x_trait_cost" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_TREE_X_TRAIT_COST, "SELECT MAX(ID) + 1 FROM trait_tree_x_trait_cost", CONNECTION_SYNCH); + + // TraitTreeXTraitCurrency.db2 + PrepareStatement(HOTFIX_SEL_TRAIT_TREE_X_TRAIT_CURRENCY, "SELECT ID, `Index`, TraitTreeID, TraitCurrencyID FROM trait_tree_x_trait_currency" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_TRAIT_TREE_X_TRAIT_CURRENCY, "SELECT MAX(ID) + 1 FROM trait_tree_x_trait_currency", CONNECTION_SYNCH); + // TransmogIllusion.db2 PrepareStatement(HOTFIX_SEL_TRANSMOG_ILLUSION, "SELECT ID, UnlockConditionID, TransmogCost, SpellItemEnchantmentID, Flags FROM transmog_illusion" " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h index 7816a6e7de1..46da89df7b0 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.h +++ b/src/server/database/Database/Implementation/HotfixDatabase.h @@ -777,6 +777,9 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_SKILL_LINE_ABILITY_MAX_ID, HOTFIX_SEL_SKILL_LINE_ABILITY_LOCALE, + HOTFIX_SEL_SKILL_LINE_X_TRAIT_TREE, + HOTFIX_SEL_SKILL_LINE_X_TRAIT_TREE_MAX_ID, + HOTFIX_SEL_SKILL_RACE_CLASS_INFO, HOTFIX_SEL_SKILL_RACE_CLASS_INFO_MAX_ID, @@ -944,6 +947,77 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_TRANSMOG_HOLIDAY, HOTFIX_SEL_TRANSMOG_HOLIDAY_MAX_ID, + HOTFIX_SEL_TRAIT_COND, + HOTFIX_SEL_TRAIT_COND_MAX_ID, + + HOTFIX_SEL_TRAIT_COST, + HOTFIX_SEL_TRAIT_COST_MAX_ID, + + HOTFIX_SEL_TRAIT_CURRENCY, + HOTFIX_SEL_TRAIT_CURRENCY_MAX_ID, + + HOTFIX_SEL_TRAIT_CURRENCY_SOURCE, + HOTFIX_SEL_TRAIT_CURRENCY_SOURCE_MAX_ID, + HOTFIX_SEL_TRAIT_CURRENCY_SOURCE_LOCALE, + + HOTFIX_SEL_TRAIT_DEFINITION, + HOTFIX_SEL_TRAIT_DEFINITION_MAX_ID, + HOTFIX_SEL_TRAIT_DEFINITION_LOCALE, + + HOTFIX_SEL_TRAIT_DEFINITION_EFFECT_POINTS, + HOTFIX_SEL_TRAIT_DEFINITION_EFFECT_POINTS_MAX_ID, + + HOTFIX_SEL_TRAIT_EDGE, + HOTFIX_SEL_TRAIT_EDGE_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE, + HOTFIX_SEL_TRAIT_NODE_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_ENTRY, + HOTFIX_SEL_TRAIT_NODE_ENTRY_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND, + HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST, + HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_GROUP, + HOTFIX_SEL_TRAIT_NODE_GROUP_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COND, + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COND_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COST, + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COST_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE, + HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COND, + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COND_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COST, + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COST_MAX_ID, + + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY, + HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY_MAX_ID, + + HOTFIX_SEL_TRAIT_TREE, + HOTFIX_SEL_TRAIT_TREE_MAX_ID, + + HOTFIX_SEL_TRAIT_TREE_LOADOUT, + HOTFIX_SEL_TRAIT_TREE_LOADOUT_MAX_ID, + + HOTFIX_SEL_TRAIT_TREE_LOADOUT_ENTRY, + HOTFIX_SEL_TRAIT_TREE_LOADOUT_ENTRY_MAX_ID, + + HOTFIX_SEL_TRAIT_TREE_X_TRAIT_COST, + HOTFIX_SEL_TRAIT_TREE_X_TRAIT_COST_MAX_ID, + + HOTFIX_SEL_TRAIT_TREE_X_TRAIT_CURRENCY, + HOTFIX_SEL_TRAIT_TREE_X_TRAIT_CURRENCY_MAX_ID, + HOTFIX_SEL_TRANSMOG_ILLUSION, HOTFIX_SEL_TRANSMOG_ILLUSION_MAX_ID, diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index e1c385dc881..66a6079b2a9 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -3992,13 +3992,17 @@ int32 GetUnitConditionVariable(Unit const* unit, Unit const* otherUnit, UnitCond case UnitConditionVariable::IsEnemy: return otherUnit && unit->GetReactionTo(otherUnit) <= REP_HOSTILE; case UnitConditionVariable::IsSpecMelee: - return unit->IsPlayer() && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Flags & CHR_SPECIALIZATION_FLAG_MELEE; + return unit->IsPlayer() && unit->ToPlayer()->GetPrimarySpecialization() + && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Flags & CHR_SPECIALIZATION_FLAG_MELEE; case UnitConditionVariable::IsSpecTank: - return unit->IsPlayer() && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Role == 0; + return unit->IsPlayer() && unit->ToPlayer()->GetPrimarySpecialization() + && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Role == 0; case UnitConditionVariable::IsSpecRanged: - return unit->IsPlayer() && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Flags & CHR_SPECIALIZATION_FLAG_RANGED; + return unit->IsPlayer() && unit->ToPlayer()->GetPrimarySpecialization() + && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Flags & CHR_SPECIALIZATION_FLAG_RANGED; case UnitConditionVariable::IsSpecHealer: - return unit->IsPlayer() && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Role == 1; + return unit->IsPlayer() && unit->ToPlayer()->GetPrimarySpecialization() + && sChrSpecializationStore.AssertEntry(unit->ToPlayer()->GetPrimarySpecialization())->Role == 1; case UnitConditionVariable::IsPlayerControlledNPC: return unit->IsCreature() && unit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); case UnitConditionVariable::IsDying: diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h index 53fd6206137..6fa74823672 100644 --- a/src/server/game/DataStores/DB2LoadInfo.h +++ b/src/server/game/DataStores/DB2LoadInfo.h @@ -5134,6 +5134,22 @@ struct SkillLineAbilityLoadInfo } }; +struct SkillLineXTraitTreeLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "SkillLineID" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "OrderIndex" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), SkillLineXTraitTreeMeta::Instance(), HOTFIX_SEL_SKILL_LINE_X_TRAIT_TREE); + return &loadInfo; + } +}; + struct SkillRaceClassInfoLoadInfo { static DB2LoadInfo const* Instance() @@ -6314,6 +6330,396 @@ struct TransmogHolidayLoadInfo } }; +struct TraitCondLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "CondType" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "GrantedRanks" }, + { true, FT_INT, "QuestID" }, + { true, FT_INT, "AchievementID" }, + { true, FT_INT, "SpecSetID" }, + { true, FT_INT, "TraitNodeGroupID" }, + { true, FT_INT, "TraitNodeID" }, + { true, FT_INT, "TraitCurrencyID" }, + { true, FT_INT, "SpentAmountRequired" }, + { true, FT_INT, "Flags" }, + { true, FT_INT, "RequiredLevel" }, + { true, FT_INT, "FreeSharedStringID" }, + { true, FT_INT, "SpendMoreSharedStringID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitCondMeta::Instance(), HOTFIX_SEL_TRAIT_COND); + return &loadInfo; + } +}; + +struct TraitCostLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_STRING_NOT_LOCALIZED, "InternalName" }, + { false, FT_INT, "ID" }, + { true, FT_INT, "Amount" }, + { true, FT_INT, "TraitCurrencyID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitCostMeta::Instance(), HOTFIX_SEL_TRAIT_COST); + return &loadInfo; + } +}; + +struct TraitCurrencyLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "Type" }, + { true, FT_INT, "CurrencyTypesID" }, + { true, FT_INT, "Flags" }, + { true, FT_INT, "Icon" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitCurrencyMeta::Instance(), HOTFIX_SEL_TRAIT_CURRENCY); + return &loadInfo; + } +}; + +struct TraitCurrencySourceLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_STRING, "Requirement" }, + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitCurrencyID" }, + { true, FT_INT, "Amount" }, + { true, FT_INT, "QuestID" }, + { true, FT_INT, "AchievementID" }, + { true, FT_INT, "PlayerLevel" }, + { true, FT_INT, "TraitNodeEntryID" }, + { true, FT_INT, "OrderIndex" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitCurrencySourceMeta::Instance(), HOTFIX_SEL_TRAIT_CURRENCY_SOURCE); + return &loadInfo; + } +}; + +struct TraitDefinitionLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_STRING, "OverrideName" }, + { false, FT_STRING, "OverrideSubtext" }, + { false, FT_STRING, "OverrideDescription" }, + { false, FT_INT, "ID" }, + { true, FT_INT, "SpellID" }, + { true, FT_INT, "OverrideIcon" }, + { true, FT_INT, "OverridesSpellID" }, + { true, FT_INT, "VisibleSpellID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitDefinitionMeta::Instance(), HOTFIX_SEL_TRAIT_DEFINITION); + return &loadInfo; + } +}; + +struct TraitDefinitionEffectPointsLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitDefinitionID" }, + { true, FT_INT, "EffectIndex" }, + { true, FT_INT, "OperationType" }, + { true, FT_INT, "CurveID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitDefinitionEffectPointsMeta::Instance(), HOTFIX_SEL_TRAIT_DEFINITION_EFFECT_POINTS); + return &loadInfo; + } +}; + +struct TraitEdgeLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "VisualStyle" }, + { true, FT_INT, "LeftTraitNodeID" }, + { true, FT_INT, "RightTraitNodeID" }, + { true, FT_INT, "Type" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitEdgeMeta::Instance(), HOTFIX_SEL_TRAIT_EDGE); + return &loadInfo; + } +}; + +struct TraitNodeLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "PosX" }, + { true, FT_INT, "PosY" }, + { true, FT_BYTE, "Type" }, + { true, FT_INT, "Flags" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeMeta::Instance(), HOTFIX_SEL_TRAIT_NODE); + return &loadInfo; + } +}; + +struct TraitNodeEntryLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitDefinitionID" }, + { true, FT_INT, "MaxRanks" }, + { false, FT_BYTE, "NodeEntryType" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeEntryMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_ENTRY); + return &loadInfo; + } +}; + +struct TraitNodeEntryXTraitCondLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitCondID" }, + { false, FT_INT, "TraitNodeEntryID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeEntryXTraitCondMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND); + return &loadInfo; + } +}; + +struct TraitNodeEntryXTraitCostLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitNodeEntryID" }, + { true, FT_INT, "TraitCostID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeEntryXTraitCostMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST); + return &loadInfo; + } +}; + +struct TraitNodeGroupLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "Flags" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeGroupMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_GROUP); + return &loadInfo; + } +}; + +struct TraitNodeGroupXTraitCondLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitCondID" }, + { true, FT_INT, "TraitNodeGroupID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeGroupXTraitCondMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COND); + return &loadInfo; + } +}; + +struct TraitNodeGroupXTraitCostLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitNodeGroupID" }, + { true, FT_INT, "TraitCostID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeGroupXTraitCostMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_COST); + return &loadInfo; + } +}; + +struct TraitNodeGroupXTraitNodeLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitNodeGroupID" }, + { true, FT_INT, "TraitNodeID" }, + { true, FT_INT, "Index" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeGroupXTraitNodeMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE); + return &loadInfo; + } +}; + +struct TraitNodeXTraitCondLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitCondID" }, + { true, FT_INT, "TraitNodeID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeXTraitCondMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COND); + return &loadInfo; + } +}; + +struct TraitNodeXTraitCostLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { false, FT_INT, "TraitNodeID" }, + { true, FT_INT, "TraitCostID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeXTraitCostMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_X_TRAIT_COST); + return &loadInfo; + } +}; + +struct TraitNodeXTraitNodeEntryLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitNodeID" }, + { true, FT_INT, "TraitNodeEntryID" }, + { true, FT_INT, "Index" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitNodeXTraitNodeEntryMeta::Instance(), HOTFIX_SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY); + return &loadInfo; + } +}; + +struct TraitTreeLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitSystemID" }, + { true, FT_INT, "Unused1000_1" }, + { true, FT_INT, "FirstTraitNodeID" }, + { true, FT_INT, "PlayerConditionID" }, + { true, FT_INT, "Flags" }, + { false, FT_FLOAT, "Unused1000_2" }, + { false, FT_FLOAT, "Unused1000_3" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitTreeMeta::Instance(), HOTFIX_SEL_TRAIT_TREE); + return &loadInfo; + } +}; + +struct TraitTreeLoadoutLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "ChrSpecializationID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitTreeLoadoutMeta::Instance(), HOTFIX_SEL_TRAIT_TREE_LOADOUT); + return &loadInfo; + } +}; + +struct TraitTreeLoadoutEntryLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "TraitTreeLoadoutID" }, + { true, FT_INT, "SelectedTraitNodeID" }, + { true, FT_INT, "SelectedTraitNodeEntryID" }, + { true, FT_INT, "NumPoints" }, + { true, FT_INT, "OrderIndex" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitTreeLoadoutEntryMeta::Instance(), HOTFIX_SEL_TRAIT_TREE_LOADOUT_ENTRY); + return &loadInfo; + } +}; + +struct TraitTreeXTraitCostLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { false, FT_INT, "TraitTreeID" }, + { true, FT_INT, "TraitCostID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitTreeXTraitCostMeta::Instance(), HOTFIX_SEL_TRAIT_TREE_X_TRAIT_COST); + return &loadInfo; + } +}; + +struct TraitTreeXTraitCurrencyLoadInfo +{ + static DB2LoadInfo const* Instance() + { + static constexpr DB2FieldMeta fields[] = + { + { false, FT_INT, "ID" }, + { true, FT_INT, "Index" }, + { true, FT_INT, "TraitTreeID" }, + { true, FT_INT, "TraitCurrencyID" }, + }; + static DB2LoadInfo const loadInfo(&fields[0], std::size(fields), TraitTreeXTraitCurrencyMeta::Instance(), HOTFIX_SEL_TRAIT_TREE_X_TRAIT_CURRENCY); + return &loadInfo; + } +}; + struct TransmogIllusionLoadInfo { static DB2LoadInfo const* Instance() diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 5ba794df95c..c184584e7d3 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -265,6 +265,7 @@ DB2Storage<SceneScriptPackageEntry> sSceneScriptPackageStore("SceneS DB2Storage<SceneScriptTextEntry> sSceneScriptTextStore("SceneScriptText.db2", SceneScriptTextLoadInfo::Instance()); DB2Storage<SkillLineEntry> sSkillLineStore("SkillLine.db2", SkillLineLoadInfo::Instance()); DB2Storage<SkillLineAbilityEntry> sSkillLineAbilityStore("SkillLineAbility.db2", SkillLineAbilityLoadInfo::Instance()); +DB2Storage<SkillLineXTraitTreeEntry> sSkillLineXTraitTreeStore("SkillLineXTraitTree.db2", SkillLineXTraitTreeLoadInfo::Instance()); DB2Storage<SkillRaceClassInfoEntry> sSkillRaceClassInfoStore("SkillRaceClassInfo.db2", SkillRaceClassInfoLoadInfo::Instance()); DB2Storage<SoulbindConduitRankEntry> sSoulbindConduitRankStore("SoulbindConduitRank.db2", SoulbindConduitRankLoadInfo::Instance()); DB2Storage<SoundKitEntry> sSoundKitStore("SoundKit.db2", SoundKitLoadInfo::Instance()); @@ -316,6 +317,29 @@ DB2Storage<TaxiPathEntry> sTaxiPathStore("TaxiPath.db2", T DB2Storage<TaxiPathNodeEntry> sTaxiPathNodeStore("TaxiPathNode.db2", TaxiPathNodeLoadInfo::Instance()); DB2Storage<TotemCategoryEntry> sTotemCategoryStore("TotemCategory.db2", TotemCategoryLoadInfo::Instance()); DB2Storage<ToyEntry> sToyStore("Toy.db2", ToyLoadInfo::Instance()); +DB2Storage<TraitCondEntry> sTraitCondStore("TraitCond.db2", TraitCondLoadInfo::Instance()); +DB2Storage<TraitCostEntry> sTraitCostStore("TraitCost.db2", TraitCostLoadInfo::Instance()); +DB2Storage<TraitCurrencyEntry> sTraitCurrencyStore("TraitCurrency.db2", TraitCurrencyLoadInfo::Instance()); +DB2Storage<TraitCurrencySourceEntry> sTraitCurrencySourceStore("TraitCurrencySource.db2", TraitCurrencySourceLoadInfo::Instance()); +DB2Storage<TraitDefinitionEntry> sTraitDefinitionStore("TraitDefinition.db2", TraitDefinitionLoadInfo::Instance()); +DB2Storage<TraitDefinitionEffectPointsEntry> sTraitDefinitionEffectPointsStore("TraitDefinitionEffectPoints.db2", TraitDefinitionEffectPointsLoadInfo::Instance()); +DB2Storage<TraitEdgeEntry> sTraitEdgeStore("TraitEdge.db2", TraitEdgeLoadInfo::Instance()); +DB2Storage<TraitNodeEntry> sTraitNodeStore("TraitNode.db2", TraitNodeLoadInfo::Instance()); +DB2Storage<TraitNodeEntryEntry> sTraitNodeEntryStore("TraitNodeEntry.db2", TraitNodeEntryLoadInfo::Instance()); +DB2Storage<TraitNodeEntryXTraitCondEntry> sTraitNodeEntryXTraitCondStore("TraitNodeEntryXTraitCond.db2", TraitNodeEntryXTraitCondLoadInfo::Instance()); +DB2Storage<TraitNodeEntryXTraitCostEntry> sTraitNodeEntryXTraitCostStore("TraitNodeEntryXTraitCost.db2", TraitNodeEntryXTraitCostLoadInfo::Instance()); +DB2Storage<TraitNodeGroupEntry> sTraitNodeGroupStore("TraitNodeGroup.db2", TraitNodeGroupLoadInfo::Instance()); +DB2Storage<TraitNodeGroupXTraitCondEntry> sTraitNodeGroupXTraitCondStore("TraitNodeGroupXTraitCond.db2", TraitNodeGroupXTraitCondLoadInfo::Instance()); +DB2Storage<TraitNodeGroupXTraitCostEntry> sTraitNodeGroupXTraitCostStore("TraitNodeGroupXTraitCost.db2", TraitNodeGroupXTraitCostLoadInfo::Instance()); +DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGroupXTraitNodeStore("TraitNodeGroupXTraitNode.db2", TraitNodeGroupXTraitNodeLoadInfo::Instance()); +DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore("TraitNodeXTraitCond.db2", TraitNodeXTraitCondLoadInfo::Instance()); +DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore("TraitNodeXTraitCost.db2", TraitNodeXTraitCostLoadInfo::Instance()); +DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore("TraitNodeXTraitNodeEntry.db2", TraitNodeXTraitNodeEntryLoadInfo::Instance()); +DB2Storage<TraitTreeEntry> sTraitTreeStore("TraitTree.db2", TraitTreeLoadInfo::Instance()); +DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore("TraitTreeLoadout.db2", TraitTreeLoadoutLoadInfo::Instance()); +DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore("TraitTreeLoadoutEntry.db2", TraitTreeLoadoutEntryLoadInfo::Instance()); +DB2Storage<TraitTreeXTraitCostEntry> sTraitTreeXTraitCostStore("TraitTreeXTraitCost.db2", TraitTreeXTraitCostLoadInfo::Instance()); +DB2Storage<TraitTreeXTraitCurrencyEntry> sTraitTreeXTraitCurrencyStore("TraitTreeXTraitCurrency.db2", TraitTreeXTraitCurrencyLoadInfo::Instance()); DB2Storage<TransmogHolidayEntry> sTransmogHolidayStore("TransmogHoliday.db2", TransmogHolidayLoadInfo::Instance()); DB2Storage<TransmogIllusionEntry> sTransmogIllusionStore("TransmogIllusion.db2", TransmogIllusionLoadInfo::Instance()); DB2Storage<TransmogSetEntry> sTransmogSetStore("TransmogSet.db2", TransmogSetLoadInfo::Instance()); @@ -841,6 +865,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sSceneScriptTextStore); LOAD_DB2(sSkillLineStore); LOAD_DB2(sSkillLineAbilityStore); + LOAD_DB2(sSkillLineXTraitTreeStore); LOAD_DB2(sSkillRaceClassInfoStore); LOAD_DB2(sSoulbindConduitRankStore); LOAD_DB2(sSoundKitStore); @@ -892,6 +917,29 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sTaxiPathNodeStore); LOAD_DB2(sTotemCategoryStore); LOAD_DB2(sToyStore); + LOAD_DB2(sTraitCondStore); + LOAD_DB2(sTraitCostStore); + LOAD_DB2(sTraitCurrencyStore); + LOAD_DB2(sTraitCurrencySourceStore); + LOAD_DB2(sTraitDefinitionStore); + LOAD_DB2(sTraitDefinitionEffectPointsStore); + LOAD_DB2(sTraitEdgeStore); + LOAD_DB2(sTraitNodeStore); + LOAD_DB2(sTraitNodeEntryStore); + LOAD_DB2(sTraitNodeEntryXTraitCondStore); + LOAD_DB2(sTraitNodeEntryXTraitCostStore); + LOAD_DB2(sTraitNodeGroupStore); + LOAD_DB2(sTraitNodeGroupXTraitCondStore); + LOAD_DB2(sTraitNodeGroupXTraitCostStore); + LOAD_DB2(sTraitNodeGroupXTraitNodeStore); + LOAD_DB2(sTraitNodeXTraitCondStore); + LOAD_DB2(sTraitNodeXTraitCostStore); + LOAD_DB2(sTraitNodeXTraitNodeEntryStore); + LOAD_DB2(sTraitTreeStore); + LOAD_DB2(sTraitTreeLoadoutStore); + LOAD_DB2(sTraitTreeLoadoutEntryStore); + LOAD_DB2(sTraitTreeXTraitCostStore); + LOAD_DB2(sTraitTreeXTraitCurrencyStore); LOAD_DB2(sTransmogHolidayStore); LOAD_DB2(sTransmogIllusionStore); LOAD_DB2(sTransmogSetStore); @@ -2942,22 +2990,30 @@ std::vector<SkillLineAbilityEntry const*> const* DB2Manager::GetSkillLineAbiliti return Trinity::Containers::MapGetValuePtr(_skillLineAbilitiesBySkillupSkill, skillId); } -SkillRaceClassInfoEntry const* DB2Manager::GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_) +SkillRaceClassInfoEntry const* DB2Manager::GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_) const { - auto bounds = _skillRaceClassInfoBySkill.equal_range(skill); - for (auto itr = bounds.first; itr != bounds.second; ++itr) + for (auto&& [_, skllRaceClassInfo] : Trinity::Containers::MapEqualRange(_skillRaceClassInfoBySkill, skill)) { - if (!itr->second->RaceMask.IsEmpty() && !(itr->second->RaceMask.HasRace(race))) + if (!skllRaceClassInfo->RaceMask.IsEmpty() && !(skllRaceClassInfo->RaceMask.HasRace(race))) continue; - if (itr->second->ClassMask && !(itr->second->ClassMask & (1 << (class_ - 1)))) + if (skllRaceClassInfo->ClassMask && !(skllRaceClassInfo->ClassMask & (1 << (class_ - 1)))) continue; - return itr->second; + return skllRaceClassInfo; } return nullptr; } +std::vector<SkillRaceClassInfoEntry const*> DB2Manager::GetSkillRaceClassInfo(uint32 skill) const +{ + std::vector<SkillRaceClassInfoEntry const*> result; + for (auto const& [_, skillRaceClassInfo] : Trinity::Containers::MapEqualRange(_skillRaceClassInfoBySkill, skill)) + result.push_back(skillRaceClassInfo); + + return result; +} + SoulbindConduitRankEntry const* DB2Manager::GetSoulbindConduitRank(int32 soulbindConduitId, int32 rank) const { return Trinity::Containers::MapGetValuePtr(_soulbindConduitRanks, { soulbindConduitId, rank }); diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h index 6b232820363..607cda14e82 100644 --- a/src/server/game/DataStores/DB2Stores.h +++ b/src/server/game/DataStores/DB2Stores.h @@ -195,6 +195,7 @@ TC_GAME_API extern DB2Storage<ScenarioStepEntry> sScenarioSte TC_GAME_API extern DB2Storage<SkillLineEntry> sSkillLineStore; TC_GAME_API extern DB2Storage<SceneScriptPackageEntry> sSceneScriptPackageStore; TC_GAME_API extern DB2Storage<SkillLineAbilityEntry> sSkillLineAbilityStore; +TC_GAME_API extern DB2Storage<SkillLineXTraitTreeEntry> sSkillLineXTraitTreeStore; TC_GAME_API extern DB2Storage<SkillRaceClassInfoEntry> sSkillRaceClassInfoStore; TC_GAME_API extern DB2Storage<SoundKitEntry> sSoundKitStore; TC_GAME_API extern DB2Storage<SpellAuraOptionsEntry> sSpellAuraOptionsStore; @@ -237,6 +238,29 @@ TC_GAME_API extern DB2Storage<SummonPropertiesEntry> sSummonPrope TC_GAME_API extern DB2Storage<TalentEntry> sTalentStore; TC_GAME_API extern DB2Storage<TaxiNodesEntry> sTaxiNodesStore; TC_GAME_API extern DB2Storage<TaxiPathEntry> sTaxiPathStore; +TC_GAME_API extern DB2Storage<TraitCondEntry> sTraitCondStore; +TC_GAME_API extern DB2Storage<TraitCostEntry> sTraitCostStore; +TC_GAME_API extern DB2Storage<TraitCurrencyEntry> sTraitCurrencyStore; +TC_GAME_API extern DB2Storage<TraitCurrencySourceEntry> sTraitCurrencySourceStore; +TC_GAME_API extern DB2Storage<TraitDefinitionEntry> sTraitDefinitionStore; +TC_GAME_API extern DB2Storage<TraitDefinitionEffectPointsEntry> sTraitDefinitionEffectPointsStore; +TC_GAME_API extern DB2Storage<TraitEdgeEntry> sTraitEdgeStore; +TC_GAME_API extern DB2Storage<TraitNodeEntry> sTraitNodeStore; +TC_GAME_API extern DB2Storage<TraitNodeEntryEntry> sTraitNodeEntryStore; +TC_GAME_API extern DB2Storage<TraitNodeEntryXTraitCondEntry> sTraitNodeEntryXTraitCondStore; +TC_GAME_API extern DB2Storage<TraitNodeEntryXTraitCostEntry> sTraitNodeEntryXTraitCostStore; +TC_GAME_API extern DB2Storage<TraitNodeGroupEntry> sTraitNodeGroupStore; +TC_GAME_API extern DB2Storage<TraitNodeGroupXTraitCondEntry> sTraitNodeGroupXTraitCondStore; +TC_GAME_API extern DB2Storage<TraitNodeGroupXTraitCostEntry> sTraitNodeGroupXTraitCostStore; +TC_GAME_API extern DB2Storage<TraitNodeGroupXTraitNodeEntry> sTraitNodeGroupXTraitNodeStore; +TC_GAME_API extern DB2Storage<TraitNodeXTraitCondEntry> sTraitNodeXTraitCondStore; +TC_GAME_API extern DB2Storage<TraitNodeXTraitCostEntry> sTraitNodeXTraitCostStore; +TC_GAME_API extern DB2Storage<TraitNodeXTraitNodeEntryEntry> sTraitNodeXTraitNodeEntryStore; +TC_GAME_API extern DB2Storage<TraitTreeEntry> sTraitTreeStore; +TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntry> sTraitTreeLoadoutStore; +TC_GAME_API extern DB2Storage<TraitTreeLoadoutEntryEntry> sTraitTreeLoadoutEntryStore; +TC_GAME_API extern DB2Storage<TraitTreeXTraitCostEntry> sTraitTreeXTraitCostStore; +TC_GAME_API extern DB2Storage<TraitTreeXTraitCurrencyEntry> sTraitTreeXTraitCurrencyStore; TC_GAME_API extern DB2Storage<TransmogHolidayEntry> sTransmogHolidayStore; TC_GAME_API extern DB2Storage<TransmogIllusionEntry> sTransmogIllusionStore; TC_GAME_API extern DB2Storage<TransmogSetEntry> sTransmogSetStore; @@ -452,7 +476,8 @@ public: ShapeshiftFormModelData const* GetShapeshiftFormModelData(uint8 race, uint8 gender, uint8 form) const; std::vector<SkillLineEntry const*> const* GetSkillLinesForParentSkill(uint32 parentSkillId) const; std::vector<SkillLineAbilityEntry const*> const* GetSkillLineAbilitiesBySkill(uint32 skillId) const; - SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_); + SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_) const; + std::vector<SkillRaceClassInfoEntry const*> GetSkillRaceClassInfo(uint32 skill) const; SoulbindConduitRankEntry const* GetSoulbindConduitRank(int32 soulbindConduitId, int32 rank) const; std::vector<SpecializationSpellsEntry const*> const* GetSpecializationSpells(uint32 specId) const; bool IsSpecSetMember(int32 specSetId, uint32 specId) const; diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index 187eac98d3c..47b91ff31f0 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -3207,6 +3207,14 @@ struct SkillLineAbilityEntry EnumFlag<SkillLineAbilityFlags> GetFlags() const { return static_cast<SkillLineAbilityFlags>(Flags); } }; +struct SkillLineXTraitTreeEntry +{ + uint32 ID; + int32 SkillLineID; + int32 TraitTreeID; + int32 OrderIndex; +}; + struct SkillRaceClassInfoEntry { uint32 ID; @@ -3855,6 +3863,222 @@ struct TransmogHolidayEntry int32 RequiredTransmogHoliday; }; +struct TraitCondEntry +{ + uint32 ID; + int32 CondType; + int32 TraitTreeID; + int32 GrantedRanks; + int32 QuestID; + int32 AchievementID; + int32 SpecSetID; + int32 TraitNodeGroupID; + int32 TraitNodeID; + int32 TraitCurrencyID; + int32 SpentAmountRequired; + int32 Flags; + int32 RequiredLevel; + int32 FreeSharedStringID; + int32 SpendMoreSharedStringID; + + TraitConditionType GetCondType() const { return static_cast<TraitConditionType>(CondType); } +}; + +struct TraitCostEntry +{ + char const* InternalName; + uint32 ID; + int32 Amount; + int32 TraitCurrencyID; +}; + +struct TraitCurrencyEntry +{ + uint32 ID; + int32 Type; + int32 CurrencyTypesID; + int32 Flags; + int32 Icon; + + TraitCurrencyType GetType() const { return static_cast<TraitCurrencyType>(Type); } +}; + +struct TraitCurrencySourceEntry +{ + LocalizedString Requirement; + uint32 ID; + int32 TraitCurrencyID; + int32 Amount; + int32 QuestID; + int32 AchievementID; + int32 PlayerLevel; + int32 TraitNodeEntryID; + int32 OrderIndex; +}; + +struct TraitDefinitionEntry +{ + LocalizedString OverrideName; + LocalizedString OverrideSubtext; + LocalizedString OverrideDescription; + uint32 ID; + int32 SpellID; + int32 OverrideIcon; + int32 OverridesSpellID; + int32 VisibleSpellID; +}; + +struct TraitDefinitionEffectPointsEntry +{ + uint32 ID; + int32 TraitDefinitionID; + int32 EffectIndex; + int32 OperationType; + int32 CurveID; + + TraitPointsOperationType GetOperationType() const { return static_cast<TraitPointsOperationType>(OperationType); } +}; + +struct TraitEdgeEntry +{ + uint32 ID; + int32 VisualStyle; + int32 LeftTraitNodeID; + int32 RightTraitNodeID; + int32 Type; +}; + +struct TraitNodeEntry +{ + uint32 ID; + int32 TraitTreeID; + int32 PosX; + int32 PosY; + int8 Type; + int32 Flags; + + TraitNodeType GetType() const { return static_cast<TraitNodeType>(Type); } +}; + +struct TraitNodeEntryEntry +{ + uint32 ID; + int32 TraitDefinitionID; + int32 MaxRanks; + uint8 NodeEntryType; +}; + +struct TraitNodeEntryXTraitCondEntry +{ + uint32 ID; + int32 TraitCondID; + uint32 TraitNodeEntryID; +}; + +struct TraitNodeEntryXTraitCostEntry +{ + uint32 ID; + int32 TraitNodeEntryID; + int32 TraitCostID; +}; + +struct TraitNodeGroupEntry +{ + uint32 ID; + int32 TraitTreeID; + int32 Flags; +}; + +struct TraitNodeGroupXTraitCondEntry +{ + uint32 ID; + int32 TraitCondID; + int32 TraitNodeGroupID; +}; + +struct TraitNodeGroupXTraitCostEntry +{ + uint32 ID; + int32 TraitNodeGroupID; + int32 TraitCostID; +}; + +struct TraitNodeGroupXTraitNodeEntry +{ + uint32 ID; + int32 TraitNodeGroupID; + int32 TraitNodeID; + int32 Index; +}; + +struct TraitNodeXTraitCondEntry +{ + uint32 ID; + int32 TraitCondID; + int32 TraitNodeID; +}; + +struct TraitNodeXTraitCostEntry +{ + uint32 ID; + uint32 TraitNodeID; + int32 TraitCostID; +}; + +struct TraitNodeXTraitNodeEntryEntry +{ + uint32 ID; + int32 TraitNodeID; + int32 TraitNodeEntryID; + int32 Index; +}; + +struct TraitTreeEntry +{ + uint32 ID; + int32 TraitSystemID; + int32 Unused1000_1; + int32 FirstTraitNodeID; + int32 PlayerConditionID; + int32 Flags; + float Unused1000_2; + float Unused1000_3; + + EnumFlag<TraitTreeFlag> GetFlags() const { return static_cast<TraitTreeFlag>(Flags); } +}; + +struct TraitTreeLoadoutEntry +{ + uint32 ID; + int32 TraitTreeID; + int32 ChrSpecializationID; +}; + +struct TraitTreeLoadoutEntryEntry +{ + uint32 ID; + int32 TraitTreeLoadoutID; + int32 SelectedTraitNodeID; + int32 SelectedTraitNodeEntryID; + int32 NumPoints; + int32 OrderIndex; +}; + +struct TraitTreeXTraitCostEntry +{ + uint32 ID; + uint32 TraitTreeID; + int32 TraitCostID; +}; + +struct TraitTreeXTraitCurrencyEntry +{ + uint32 ID; + int32 Index; + int32 TraitTreeID; + int32 TraitCurrencyID; +}; + struct TransmogIllusionEntry { uint32 ID; diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index 4590b5909c9..6de7baedc9c 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -1893,6 +1893,24 @@ enum class TraitCombatConfigFlags : int32 DEFINE_ENUM_FLAG(TraitCombatConfigFlags); +enum class TraitCondFlags : int32 +{ + None = 0x0, + IsGate = 0x1, + IsAlwaysMet = 0x2, + IsSufficient = 0x4, +}; + +DEFINE_ENUM_FLAG(TraitCondFlags); + +enum class TraitConditionType : int32 +{ + Available = 0, + Visible = 1, + Granted = 2, + Increased = 3 +}; + enum class TraitConfigType : int32 { Invalid = 0, @@ -1901,6 +1919,68 @@ enum class TraitConfigType : int32 Generic = 3 }; +enum class TraitCurrencyType : int32 +{ + Gold = 0, + CurrencyTypesBased = 1, + TraitSourced = 2 +}; + +enum class TraitEdgeType : int32 +{ + VisualOnly = 0, + DeprecatedRankConnection = 1, + SufficientForAvailability = 2, + RequiredForAvailability = 3, + MutuallyExclusive = 4, + DeprecatedSelectionOption = 5 +}; + +enum class TraitNodeEntryType : int32 +{ + SpendHex = 0, + SpendSquare = 1, + SpendCircle = 2, + SpendSmallCircle = 3, + DeprecatedSelect = 4, + DragAndDrop = 5, + SpendDiamond = 6, + ProfPath = 7, + ProfPerk = 8, + ProfPathUnlock = 9 +}; + +enum class TraitNodeGroupFlag : int32 +{ + None = 0x0, + AvailableByDefault = 0x1 +}; + +DEFINE_ENUM_FLAG(TraitNodeGroupFlag); + +enum class TraitNodeType : int32 +{ + Single = 0, + Tiered = 1, + Selection = 2 +}; + +enum class TraitPointsOperationType : int32 +{ + None = -1, + Set = 0, + Multiply = 1 +}; + +enum class TraitTreeFlag : int32 +{ + None = 0x0, + CannotRefund = 0x1, + HideSingleRankNumbers = 0x2 +}; + +DEFINE_ENUM_FLAG(TraitTreeFlag); + enum class UiMapFlag : int32 { None = 0, diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index ad5e1d2a632..178a053c5b3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -118,6 +118,8 @@ #include "TerrainMgr.h" #include "ToyPackets.h" #include "TradeData.h" +#include "TraitMgr.h" +#include "TraitPacketsCommon.h" #include "Transport.h" #include "UpdateData.h" #include "Util.h" @@ -2802,7 +2804,7 @@ WorldLocation const* Player::GetStoredAuraTeleportLocation(uint32 spellId) const return nullptr; } -bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/) +bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, int32 fromSkill /*= 0*/, Optional<int32> traitDefinitionId /*= {}*/) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE); if (!spellInfo) @@ -2882,6 +2884,15 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent dependent_set = true; } + if (itr->second.TraitDefinitionId != traitDefinitionId) + { + if (itr->second.TraitDefinitionId) + if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*itr->second.TraitDefinitionId)) + RemoveOverrideSpell(traitDefinition->OverridesSpellID, spellId); + + itr->second.TraitDefinitionId = traitDefinitionId; + } + // update active state for known spell if (itr->second.active != active && itr->second.state != PLAYERSPELL_REMOVED && !itr->second.disabled) { @@ -2963,6 +2974,8 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent newspell.active = active; newspell.dependent = dependent; newspell.disabled = disabled; + if (traitDefinitionId) + newspell.TraitDefinitionId = *traitDefinitionId; // replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible if (newspell.active && !newspell.disabled && spellInfo->IsRanked()) @@ -3011,26 +3024,66 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent return false; } + bool castSpell = false; + // cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned) // note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL)) - { // ignore stance requirement for talent learn spell (stance set for spell only for client spell description show) - CastSpell(this, spellId, true); - } + castSpell = true; // also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks else if (spellInfo->IsPassive()) - { - if (HandlePassiveSpellLearn(spellInfo)) - CastSpell(this, spellId, true); - } + castSpell = HandlePassiveSpellLearn(spellInfo); else if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP)) + castSpell = true; + else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED)) + castSpell = true; + + if (castSpell) { - CastSpell(this, spellId, true); - return false; + CastSpellExtraArgs args; + args.SetTriggerFlags(TRIGGERED_FULL_MASK); + + if (traitDefinitionId) + { + if (UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID)) + { + int32 traitEntryIndex = traitConfig->Entries.FindIndexIf([traitDefinitionId](UF::TraitEntry const& traitEntry) + { + return sTraitNodeEntryStore.AssertEntry(traitEntry.TraitNodeEntryID)->TraitDefinitionID == traitDefinitionId; + }); + int32 rank = 0; + if (traitEntryIndex >= 0) + rank = traitConfig->Entries[traitEntryIndex].Rank + traitConfig->Entries[traitEntryIndex].GrantedRanks; + + if (rank > 0) + { + if (std::vector<TraitDefinitionEffectPointsEntry const*> const* traitDefinitionEffectPoints = TraitMgr::GetTraitDefinitionEffectPointModifiers(*traitDefinitionId)) + { + for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoint : *traitDefinitionEffectPoints) + { + if (traitDefinitionEffectPoint->EffectIndex >= int32(spellInfo->GetEffects().size())) + continue; + + float basePoints = sDB2Manager.GetCurveValueAt(traitDefinitionEffectPoint->CurveID, rank); + if (traitDefinitionEffectPoint->GetOperationType() == TraitPointsOperationType::Multiply) + basePoints *= spellInfo->GetEffect(SpellEffIndex(traitDefinitionEffectPoint->EffectIndex)).CalcBaseValue(this, nullptr, 0, -1); + + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + traitDefinitionEffectPoint->EffectIndex), basePoints); + } + } + } + } + } + + CastSpell(this, spellId, args); + if (spellInfo->HasEffect(SPELL_EFFECT_SKILL_STEP)) + return false; } - else if (spellInfo->HasAttribute(SPELL_ATTR1_CAST_WHEN_LEARNED)) - CastSpell(this, spellId, true); + + if (traitDefinitionId) + if (TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(*traitDefinitionId)) + AddOverrideSpell(traitDefinition->OverridesSpellID, spellId); // update free primary prof.points (if any, can be none in case GM .learn prof. learning) if (uint32 freeProfs = GetFreePrimaryProfessionPoints()) @@ -3168,14 +3221,14 @@ bool Player::HandlePassiveSpellLearn(SpellInfo const* spellInfo) return need_cast && (!spellInfo->CasterAuraState || HasAuraState(AuraStateType(spellInfo->CasterAuraState))); } -void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/) +void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/, bool suppressMessaging /*= false*/, Optional<int32> traitDefinitionId /*= {}*/) { PlayerSpellMap::iterator itr = m_spells.find(spell_id); bool disabled = (itr != m_spells.end()) ? itr->second.disabled : false; bool active = disabled ? itr->second.active : true; - bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill); + bool learning = AddSpell(spell_id, active, true, dependent, false, false, fromSkill, traitDefinitionId); // prevent duplicated entires in spell book, also not send if not in world (loading) if (learning && IsInWorld()) @@ -3183,6 +3236,7 @@ void Player::LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill /*= 0*/ WorldPackets::Spells::LearnedSpells learnedSpells; WorldPackets::Spells::LearnedSpellInfo& learnedSpellInfo = learnedSpells.ClientLearnedSpellData.emplace_back(); learnedSpellInfo.SpellID = spell_id; + learnedSpellInfo.TraitDefinitionID = traitDefinitionId; learnedSpells.SuppressMessaging = suppressMessaging; SendDirectMessage(learnedSpells.Write()); } @@ -4169,6 +4223,14 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe Garrison::DeleteFromDB(guid, trans); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS_BY_CHAR); + stmt->setUInt64(0, guid); + trans->Append(stmt); + sCharacterCache->DeleteCharacterCacheEntry(playerguid, name); break; } @@ -17589,6 +17651,9 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol LearnDefaultSkills(); LearnCustomSpells(); + _LoadTraits(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRAIT_CONFIGS), + holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES)); // must be after loading spells + // must be before inventory (some items required reputation check) m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION)); @@ -17606,7 +17671,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol // update items with duration and realtime UpdateItemDuration(time_diff, true); - _LoadActions(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACTIONS)); + StartLoadingActionButtons(); // unread mails and next delivery time, actual mails not loaded _LoadMail(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS), @@ -19559,6 +19624,7 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba _SaveMonthlyQuestStatus(trans); _SaveGlyphs(trans); _SaveTalents(trans); + _SaveTraits(trans); _SaveSpells(trans); GetSpellHistory()->SaveToDB<Player>(trans); _SaveActions(trans); @@ -19661,6 +19727,26 @@ void Player::_SaveCustomizations(CharacterDatabaseTransaction trans) void Player::_SaveActions(CharacterDatabaseTransaction trans) { + int32 traitConfigId = [&]() -> int32 + { + UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID); + if (!traitConfig) + return 0; + + int32 usedSavedTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([localIdent = *traitConfig->LocalIdentifier](UF::TraitConfig const& savedConfig) + { + return static_cast<TraitConfigType>(*savedConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::SharedActionBars) == TraitCombatConfigFlags::None + && savedConfig.LocalIdentifier == localIdent; + }); + + if (usedSavedTraitConfigIndex >= 0) + return m_activePlayerData->TraitConfigs[usedSavedTraitConfigIndex].ID; + + return 0; + }(); + CharacterDatabasePreparedStatement* stmt; for (ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end();) @@ -19671,9 +19757,10 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION); stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt8(1, GetActiveTalentGroup()); - stmt->setUInt8(2, itr->first); - stmt->setUInt64(3, itr->second.GetAction()); - stmt->setUInt8(4, uint8(itr->second.GetType())); + stmt->setInt32(2, traitConfigId); + stmt->setUInt8(3, itr->first); + stmt->setUInt64(4, itr->second.GetAction()); + stmt->setUInt8(5, uint8(itr->second.GetType())); trans->Append(stmt); itr->second.uState = ACTIONBUTTON_UNCHANGED; @@ -19686,6 +19773,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt->setUInt64(2, GetGUID().GetCounter()); stmt->setUInt8(3, itr->first); stmt->setUInt8(4, GetActiveTalentGroup()); + stmt->setInt32(5, traitConfigId); trans->Append(stmt); itr->second.uState = ACTIONBUTTON_UNCHANGED; @@ -19696,6 +19784,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans) stmt->setUInt64(0, GetGUID().GetCounter()); stmt->setUInt8(1, itr->first); stmt->setUInt8(2, GetActiveTalentGroup()); + stmt->setInt32(3, traitConfigId); trans->Append(stmt); m_actionButtons.erase(itr++); @@ -26493,6 +26582,146 @@ void Player::_LoadPvpTalents(PreparedQueryResult result) } } +void Player::_LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult) +{ + std::unordered_multimap<int32, WorldPackets::Traits::TraitEntry> traitEntriesByConfig; + if (entriesResult) + { + // 0 1, 2 3 4 + // SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank, grantedRanks FROM character_trait_entry WHERE guid = ? + do + { + Field* fields = entriesResult->Fetch(); + WorldPackets::Traits::TraitEntry traitEntry; + traitEntry.TraitNodeID = fields[1].GetInt32(); + traitEntry.TraitNodeEntryID = fields[2].GetInt32(); + traitEntry.Rank = fields[3].GetInt32(); + traitEntry.GrantedRanks = fields[4].GetInt32(); + + if (!TraitMgr::IsValidEntry(traitEntry)) + continue; + + traitEntriesByConfig.emplace(fields[0].GetInt32(), traitEntry); + + } while (entriesResult->NextRow()); + } + + if (configsResult) + { + // 0 1 2 3 4 5 6 7 + // SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ? + do + { + Field* fields = configsResult->Fetch(); + WorldPackets::Traits::TraitConfig traitConfig; + traitConfig.ID = fields[0].GetInt32(); + traitConfig.Type = static_cast<TraitConfigType>(fields[1].GetInt32()); + switch (traitConfig.Type) + { + case TraitConfigType::Combat: + traitConfig.ChrSpecializationID = fields[2].GetInt32(); + traitConfig.CombatConfigFlags = static_cast<TraitCombatConfigFlags>(fields[3].GetInt32()); + traitConfig.LocalIdentifier = fields[4].GetInt32(); + break; + case TraitConfigType::Profession: + traitConfig.SkillLineID = fields[5].GetInt32(); + break; + case TraitConfigType::Generic: + traitConfig.TraitSystemID = fields[6].GetInt32(); + break; + default: + break; + } + + traitConfig.Name = fields[7].GetString(); + + for (auto&& [_, traitEntry] : Trinity::Containers::MapEqualRange(traitEntriesByConfig, traitConfig.ID)) + traitConfig.Entries.emplace_back() = traitEntry; + + if (TraitMgr::ValidateConfig(traitConfig, this) != TALENT_LEARN_OK) + { + traitConfig.Entries.clear(); + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) + traitConfig.Entries.emplace_back(grantedEntry); + } + + AddTraitConfig(traitConfig); + + } while (configsResult->NextRow()); + } + + auto hasConfigForSpec = [&](int32 specId) + { + return m_activePlayerData->TraitConfigs.FindIndexIf([=](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == specId + && traitConfig.CombatConfigFlags & AsUnderlyingType(TraitCombatConfigFlags::ActiveForSpec); + }) >= 0; + }; + + auto findFreeLocalIdentifier = [&](int32 specId) + { + int32 index = 1; + while (m_activePlayerData->TraitConfigs.FindIndexIf([specId, index](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == specId + && traitConfig.LocalIdentifier == index; + }) >= 0) + ++index; + + return index; + }; + + for (uint32 i = 0; i < MAX_SPECIALIZATIONS - 1 /*initial spec doesnt get a config*/; ++i) + { + if (ChrSpecializationEntry const* spec = sDB2Manager.GetChrSpecializationByIndex(GetClass(), i)) + { + if (hasConfigForSpec(spec->ID)) + continue; + + WorldPackets::Traits::TraitConfig traitConfig; + traitConfig.Type = TraitConfigType::Combat; + traitConfig.ChrSpecializationID = spec->ID; + traitConfig.CombatConfigFlags = TraitCombatConfigFlags::ActiveForSpec; + traitConfig.LocalIdentifier = findFreeLocalIdentifier(spec->ID); + traitConfig.Name = spec->Name[GetSession()->GetSessionDbcLocale()]; + + CreateTraitConfig(traitConfig); + } + } + + int32 activeConfig = m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& traitConfig) + { + return traitConfig.Type == AsUnderlyingType(TraitConfigType::Combat) + && traitConfig.ChrSpecializationID == int32(GetPrimarySpecialization()) + && traitConfig.CombatConfigFlags & AsUnderlyingType(TraitCombatConfigFlags::ActiveForSpec); + }); + + if (activeConfig >= 0) + SetActiveCombatTraitConfigID(m_activePlayerData->TraitConfigs[activeConfig].ID); + + for (UF::TraitConfig const& traitConfig : m_activePlayerData->TraitConfigs) + { + switch (static_cast<TraitConfigType>(*traitConfig.Type)) + { + case TraitConfigType::Combat: + if (traitConfig.ID != int32(*m_activePlayerData->ActiveCombatTraitConfigID)) + continue; + break; + case TraitConfigType::Profession: + if (!HasSkill(traitConfig.SkillLineID)) + continue; + break; + default: + break; + } + + ApplyTraitConfig(traitConfig.ID, true); + } +} + void Player::_SaveTalents(CharacterDatabaseTransaction trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT); @@ -26537,6 +26766,97 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans) } } +void Player::_SaveTraits(CharacterDatabaseTransaction trans) +{ + CharacterDatabasePreparedStatement* stmt = nullptr; + for (auto& [traitConfigId, state] : m_traitConfigStates) + { + switch (state) + { + case PLAYERSPELL_CHANGED: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + if (UF::TraitConfig const* traitConfig = GetTraitConfig(traitConfigId)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfig->ID); + stmt->setInt32(2, traitConfig->Type); + switch (static_cast<TraitConfigType>(*traitConfig->Type)) + { + case TraitConfigType::Combat: + stmt->setInt32(3, traitConfig->ChrSpecializationID); + stmt->setInt32(4, traitConfig->CombatConfigFlags); + stmt->setInt32(5, traitConfig->LocalIdentifier); + stmt->setNull(6); + stmt->setNull(7); + break; + case TraitConfigType::Profession: + stmt->setNull(3); + stmt->setNull(4); + stmt->setNull(5); + stmt->setInt32(6, traitConfig->SkillLineID); + stmt->setNull(7); + break; + case TraitConfigType::Generic: + stmt->setNull(3); + stmt->setNull(4); + stmt->setNull(5); + stmt->setNull(6); + stmt->setInt32(7, traitConfig->TraitSystemID); + break; + default: + break; + } + + stmt->setString(8, traitConfig->Name); + trans->Append(stmt); + + for (UF::TraitEntry const& traitEntry : traitConfig->Entries) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfig->ID); + stmt->setInt32(2, traitEntry.TraitNodeID); + stmt->setInt32(3, traitEntry.TraitNodeEntryID); + stmt->setInt32(4, traitEntry.Rank); + stmt->setInt32(5, traitEntry.GrantedRanks); + trans->Append(stmt); + } + } + break; + case PLAYERSPELL_REMOVED: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + trans->Append(stmt); + break; + default: + break; + } + } + + m_traitConfigStates.clear(); +} + void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) { if (GetActiveTalentGroup() == spec->OrderIndex) @@ -26634,6 +26954,8 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID); } + ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false); + // Remove spec specific spells RemoveSpecializationSpells(); @@ -26642,6 +26964,16 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) SetActiveTalentGroup(spec->OrderIndex); SetPrimarySpecialization(spec->ID); + int32 specTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([spec](UF::TraitConfig const& traitConfig) + { + return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && traitConfig.ChrSpecializationID == int32(spec->ID) + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None; + }); + if (specTraitConfigIndex >= 0) + SetActiveCombatTraitConfigID(m_activePlayerData->TraitConfigs[specTraitConfigIndex].ID); + else + SetActiveCombatTraitConfigID(0); for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId) { @@ -26684,24 +27016,11 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) if (uint32 mastery = spec->MasterySpellID[i]) LearnSpell(mastery, true); + ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, true); + InitTalentForLevel(); - // load them asynchronously - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC); - stmt->setUInt64(0, GetGUID().GetCounter()); - stmt->setUInt8(1, GetActiveTalentGroup()); - - WorldSession* mySess = GetSession(); - mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) - .WithPreparedCallback([mySess](PreparedQueryResult result) - { - // safe callback, we can't pass this pointer directly - // in case player logs out before db response (player would be deleted in that case) - if (Player* thisPlayer = mySess->GetPlayer()) - thisPlayer->LoadActions(result); - })); - } + StartLoadingActionButtons(); UpdateDisplayPower(); Powers pw = GetPowerType(); @@ -26759,6 +27078,48 @@ void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec) } } +void Player::StartLoadingActionButtons(std::function<void()>&& callback /*= nullptr*/) +{ + int32 traitConfigId = [&]() -> int32 + { + UF::TraitConfig const* traitConfig = GetTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID); + if (!traitConfig) + return 0; + + int32 usedSavedTraitConfigIndex = m_activePlayerData->TraitConfigs.FindIndexIf([localIdent = *traitConfig->LocalIdentifier](UF::TraitConfig const& savedConfig) + { + return static_cast<TraitConfigType>(*savedConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None + && (static_cast<TraitCombatConfigFlags>(*savedConfig.CombatConfigFlags) & TraitCombatConfigFlags::SharedActionBars) == TraitCombatConfigFlags::None + && savedConfig.LocalIdentifier == localIdent; + }); + + if (usedSavedTraitConfigIndex >= 0) + return m_activePlayerData->TraitConfigs[usedSavedTraitConfigIndex].ID; + + return 0; + }(); + + // load them asynchronously + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setUInt8(1, GetActiveTalentGroup()); + stmt->setInt32(2, traitConfigId); + + WorldSession* mySess = GetSession(); + mySess->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt) + .WithPreparedCallback([mySess, myGuid = GetGUID(), callback = std::move(callback)](PreparedQueryResult result) + { + // safe callback, we can't pass this pointer directly + // in case player logs out before db response (player would be deleted in that case) + if (Player* thisPlayer = mySess->GetPlayer(); thisPlayer && thisPlayer->GetGUID() == myGuid) + thisPlayer->LoadActions(result); + + if (callback) + callback(); + })); +} + void Player::LoadActions(PreparedQueryResult result) { _LoadActions(result); @@ -26766,6 +27127,368 @@ void Player::LoadActions(PreparedQueryResult result) SendActionButtons(1); } +void Player::CreateTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig) +{ + uint32 configId = TraitMgr::GenerateNewTraitConfigId(); + auto hasConfigId = [&](int32 id) + { + return m_activePlayerData->TraitConfigs.FindIndexIf([id](UF::TraitConfig const& config) { return config.ID == id; }) >= 0; + }; + + while (hasConfigId(configId)) + configId = TraitMgr::GenerateNewTraitConfigId(); + + traitConfig.ID = configId; + + int32 traitConfigIndex = m_activePlayerData->TraitConfigs.size(); + AddTraitConfig(traitConfig); + + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(traitConfig, this)) + { + auto entryItr = std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), + [&](WorldPackets::Traits::TraitEntry const& entry) { return entry.TraitNodeID == grantedEntry.TraitNodeID && entry.TraitNodeEntryID == grantedEntry.TraitNodeEntryID; }); + + if (entryItr == traitConfig.Entries.end()) + AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, traitConfigIndex) + .ModifyValue(&UF::TraitConfig::Entries)) = grantedEntry; + } + + m_traitConfigStates[configId] = PLAYERSPELL_CHANGED; +} + +void Player::AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig) +{ + auto setter = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::TraitConfigs)); + setter.ModifyValue(&UF::TraitConfig::ID).SetValue(traitConfig.ID); + setter.ModifyValue(&UF::TraitConfig::Name).SetValue(traitConfig.Name); + setter.ModifyValue(&UF::TraitConfig::Type).SetValue(AsUnderlyingType(traitConfig.Type)); + setter.ModifyValue(&UF::TraitConfig::SkillLineID).SetValue(traitConfig.SkillLineID);; + setter.ModifyValue(&UF::TraitConfig::ChrSpecializationID).SetValue(traitConfig.ChrSpecializationID); + setter.ModifyValue(&UF::TraitConfig::CombatConfigFlags).SetValue(AsUnderlyingType(traitConfig.CombatConfigFlags)); + setter.ModifyValue(&UF::TraitConfig::LocalIdentifier).SetValue(traitConfig.LocalIdentifier); + setter.ModifyValue(&UF::TraitConfig::TraitSystemID).SetValue(traitConfig.TraitSystemID); + + for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries) + { + UF::TraitEntry& newEntry = AddDynamicUpdateFieldValue(setter.ModifyValue(&UF::TraitConfig::Entries)); + newEntry.TraitNodeID = traitEntry.TraitNodeID; + newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID; + newEntry.Rank = traitEntry.Rank; + newEntry.GrantedRanks = traitEntry.GrantedRanks; + } +} + +UF::TraitConfig const* Player::GetTraitConfig(int32 configId) const +{ + int32 index = m_activePlayerData->TraitConfigs.FindIndexIf([configId](UF::TraitConfig const& config) { return config.ID == configId; }); + if (index < 0) + return nullptr; + + return &m_activePlayerData->TraitConfigs[index]; +} + +void Player::UpdateTraitConfig(WorldPackets::Traits::TraitConfig&& newConfig, int32 savedConfigId, bool withCastTime) +{ + int32 index = m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& config) { return config.ID == newConfig.ID; }); + if (index < 0) + return; + + if (withCastTime) + { + CastSpell(this, TraitMgr::COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID, CastSpellExtraArgs(SPELLVALUE_BASE_POINT0, savedConfigId).SetCustomArg(std::move(newConfig))); + return; + } + + bool isActiveConfig = true; + bool loadActionButtons = false; + switch (TraitConfigType(*m_activePlayerData->TraitConfigs[index].Type)) + { + case TraitConfigType::Combat: + isActiveConfig = newConfig.ID == int32(*m_activePlayerData->ActiveCombatTraitConfigID); + loadActionButtons = m_activePlayerData->TraitConfigs[index].LocalIdentifier != newConfig.LocalIdentifier; + break; + case TraitConfigType::Profession: + isActiveConfig = HasSkill(m_activePlayerData->TraitConfigs[index].SkillLineID); + break; + default: + break; + } + + std::function<void()> finalizeTraitConfigUpdate = [=, newConfig = std::move(newConfig)]() + { + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, index) + .ModifyValue(&UF::TraitConfig::LocalIdentifier), newConfig.LocalIdentifier); + + ApplyTraitEntryChanges(newConfig.ID, newConfig, isActiveConfig, true); + + if (savedConfigId) + ApplyTraitEntryChanges(savedConfigId, newConfig, false, false); + + if (EnumFlag(newConfig.CombatConfigFlags).HasFlag(TraitCombatConfigFlags::StarterBuild)) + SetTraitConfigUseStarterBuild(newConfig.ID, true); + }; + + if (loadActionButtons) + { + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + _SaveActions(trans); + CharacterDatabase.CommitTransaction(trans); + + StartLoadingActionButtons(std::move(finalizeTraitConfigUpdate)); + } + else + finalizeTraitConfigUpdate(); +} + +void Player::ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::TraitConfig const& newConfig, bool applyTraits, bool consumeCurrencies) +{ + int32 editedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([editedConfigId](UF::TraitConfig const& config) { return config.ID == editedConfigId; }); + if (editedIndex < 0) + return; + + auto makeTraitEntryFinder = [](int32 traitNodeId, int32 traitNodeEntryId) + { + return [=](auto const& ufEntry) { return ufEntry.TraitNodeID == traitNodeId && ufEntry.TraitNodeEntryID == traitNodeEntryId; }; + }; + + UF::TraitConfig const& editedConfig = m_activePlayerData->TraitConfigs[editedIndex]; + + // remove traits not found in new config + std::set<int32, std::greater<>> entryIndicesToRemove; + for (int32 i = 0; i < int32(editedConfig.Entries.size()); ++i) + { + UF::TraitEntry const& oldEntry = editedConfig.Entries[i]; + auto entryItr = std::find_if(newConfig.Entries.begin(), newConfig.Entries.end(), makeTraitEntryFinder(oldEntry.TraitNodeID, oldEntry.TraitNodeEntryID)); + if (entryItr != newConfig.Entries.end()) + continue; + + if (applyTraits) + ApplyTraitEntry(oldEntry.TraitNodeEntryID, 0, 0, false); + + entryIndicesToRemove.insert(i); + } + + for (int32 indexToRemove : entryIndicesToRemove) + { + RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries), indexToRemove); + } + + std::vector<WorldPackets::Traits::TraitEntry> costEntries; + + // apply new traits + for (std::size_t i = 0; i < newConfig.Entries.size(); ++i) + { + WorldPackets::Traits::TraitEntry const& newEntry = newConfig.Entries[i]; + int32 oldEntryIndex = editedConfig.Entries.FindIndexIf(makeTraitEntryFinder(newEntry.TraitNodeID, newEntry.TraitNodeEntryID)); + if (oldEntryIndex < 0) + { + if (consumeCurrencies) + costEntries.push_back(newEntry); + + UF::TraitEntry& newUfEntry = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries)); + newUfEntry.TraitNodeID = newEntry.TraitNodeID; + newUfEntry.TraitNodeEntryID = newEntry.TraitNodeEntryID; + newUfEntry.Rank = newEntry.Rank; + newUfEntry.GrantedRanks = newEntry.GrantedRanks; + + if (applyTraits) + ApplyTraitEntry(newUfEntry.TraitNodeEntryID, newUfEntry.Rank, 0, true); + } + else if (newEntry.Rank != editedConfig.Entries[oldEntryIndex].Rank || newEntry.GrantedRanks != editedConfig.Entries[oldEntryIndex].GrantedRanks) + { + if (consumeCurrencies && newEntry.Rank > editedConfig.Entries[oldEntryIndex].Rank) + { + WorldPackets::Traits::TraitEntry& costEntry = costEntries.emplace_back(newEntry); + costEntry.Rank -= editedConfig.Entries[oldEntryIndex].Rank; + } + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries, oldEntryIndex) + .ModifyValue(&UF::TraitEntry::Rank), newEntry.Rank); + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Entries, oldEntryIndex) + .ModifyValue(&UF::TraitEntry::GrantedRanks), newEntry.GrantedRanks); + + if (applyTraits) + ApplyTraitEntry(newEntry.TraitNodeEntryID, newEntry.Rank, newEntry.GrantedRanks, true); + } + } + + if (consumeCurrencies) + { + std::map<int32, int32> currencies; + for (WorldPackets::Traits::TraitEntry const& costEntry : costEntries) + TraitMgr::FillSpentCurrenciesMap(costEntry, currencies); + + for (auto [traitCurrencyId, amount] : currencies) + { + TraitCurrencyEntry const* traitCurrency = sTraitCurrencyStore.LookupEntry(traitCurrencyId); + if (!traitCurrency) + continue; + + switch (traitCurrency->GetType()) + { + case TraitCurrencyType::Gold: + ModifyMoney(-amount); + break; + case TraitCurrencyType::CurrencyTypesBased: + ModifyCurrency(traitCurrency->CurrencyTypesID, -amount); + break; + default: + break; + } + } + } + + m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::RenameTraitConfig(int32 editedConfigId, std::string&& newName) +{ + int32 editedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([editedConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == editedConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (editedIndex < 0) + return; + + SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, editedIndex) + .ModifyValue(&UF::TraitConfig::Name), std::move(newName)); + + m_traitConfigStates[editedConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::DeleteTraitConfig(int32 deletedConfigId) +{ + int32 deletedIndex = m_activePlayerData->TraitConfigs.FindIndexIf([deletedConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == deletedConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (deletedIndex < 0) + return; + + RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs), deletedIndex); + + m_traitConfigStates[deletedConfigId] = PLAYERSPELL_REMOVED; +} + +void Player::ApplyTraitConfig(int32 configId, bool apply) +{ + UF::TraitConfig const* traitConfig = GetTraitConfig(configId); + if (!traitConfig) + return; + + for (UF::TraitEntry const& traitEntry : traitConfig->Entries) + ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply); +} + +void Player::ApplyTraitEntry(int32 traitNodeEntryId, int32 /*rank*/, int32 /*grantedRanks*/, bool apply) +{ + TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(traitNodeEntryId); + if (!traitNodeEntry) + return; + + TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID); + if (!traitDefinition) + return; + + if (traitDefinition->SpellID) + { + if (apply) + LearnSpell(traitDefinition->SpellID, true, 0, false, traitNodeEntry->TraitDefinitionID); + else + RemoveSpell(traitDefinition->SpellID); + } +} + +void Player::SetTraitConfigUseStarterBuild(int32 traitConfigId, bool useStarterBuild) +{ + int32 configIndex = m_activePlayerData->TraitConfigs.FindIndexIf([traitConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == traitConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None; + }); + if (configIndex < 0) + return; + + bool currentlyUsesStarterBuild = EnumFlag(static_cast<TraitCombatConfigFlags>(*m_activePlayerData->TraitConfigs[configIndex].CombatConfigFlags)).HasFlag(TraitCombatConfigFlags::StarterBuild); + if (currentlyUsesStarterBuild == useStarterBuild) + return; + + if (useStarterBuild) + SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::StarterBuild)); + else + RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::StarterBuild)); + + m_traitConfigStates[traitConfigId] = PLAYERSPELL_CHANGED; +} + +void Player::SetTraitConfigUseSharedActionBars(int32 traitConfigId, bool usesSharedActionBars, bool isLastSelectedSavedConfig) +{ + int32 configIndex = m_activePlayerData->TraitConfigs.FindIndexIf([traitConfigId](UF::TraitConfig const& traitConfig) + { + return traitConfig.ID == traitConfigId + && static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (configIndex < 0) + return; + + bool currentlyUsesSharedActionBars = EnumFlag(static_cast<TraitCombatConfigFlags>(*m_activePlayerData->TraitConfigs[configIndex].CombatConfigFlags)).HasFlag(TraitCombatConfigFlags::SharedActionBars); + if (currentlyUsesSharedActionBars == usesSharedActionBars) + return; + + if (usesSharedActionBars) + { + SetUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::SharedActionBars)); + + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_TRAIT_CONFIG); + stmt->setUInt64(0, GetGUID().GetCounter()); + stmt->setInt32(1, traitConfigId); + CharacterDatabase.Execute(stmt); + } + + if (isLastSelectedSavedConfig) + StartLoadingActionButtons(); // load action buttons that were saved in shared mode + } + else + { + RemoveUpdateFieldFlagValue(m_values.ModifyValue(&Player::m_activePlayerData) + .ModifyValue(&UF::ActivePlayerData::TraitConfigs, configIndex) + .ModifyValue(&UF::TraitConfig::CombatConfigFlags), AsUnderlyingType(TraitCombatConfigFlags::SharedActionBars)); + + // trigger a save with traitConfigId + for (auto&& [_, button] : m_actionButtons) + if (button.uState != ACTIONBUTTON_DELETED) + button.uState = ACTIONBUTTON_NEW; + } + + m_traitConfigStates[traitConfigId] = PLAYERSPELL_CHANGED; +} + void Player::SetReputation(uint32 factionentry, int32 value) { GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index ff4b3b1afa9..1c108fc4cdb 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -120,6 +120,12 @@ namespace WorldPackets { enum class UpdateCollisionHeightReason : uint8; } + + namespace Traits + { + struct TraitConfig; + struct TraitEntry; + } } TC_GAME_API uint32 GetBagSize(Bag const* bag); @@ -178,10 +184,11 @@ enum PlayerSpellState : uint8 struct PlayerSpell { - PlayerSpellState state : 8; + PlayerSpellState state; bool active : 1; // show in spellbook bool dependent : 1; // learned as result another spell learn, skill grow, quest reward, etc bool disabled : 1; // first rank has been learned in result talent learn but currently talent unlearned, save max learned ranks + Optional<int32> TraitDefinitionId; }; struct StoredAuraTeleportLocation @@ -859,7 +866,6 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_AZERITE_MILESTONE_POWERS, PLAYER_LOGIN_QUERY_LOAD_AZERITE_UNLOCKED_ESSENCES, PLAYER_LOGIN_QUERY_LOAD_AZERITE_EMPOWERED, - PLAYER_LOGIN_QUERY_LOAD_ACTIONS, PLAYER_LOGIN_QUERY_LOAD_MAILS, PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS, PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS_ARTIFACT, @@ -901,6 +907,8 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_GARRISON_BUILDINGS, PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWERS, PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWER_ABILITIES, + PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES, + PLAYER_LOGIN_QUERY_LOAD_TRAIT_CONFIGS, MAX_PLAYER_LOGIN_QUERY }; @@ -1059,7 +1067,7 @@ struct GroupUpdateCounter int32 UpdateSequenceNumber; }; -enum TalentLearnResult +enum TalentLearnResult : int32 { TALENT_LEARN_OK = 0, TALENT_FAILED_UNKNOWN = 1, @@ -1793,8 +1801,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void SendProficiency(ItemClass itemClass, uint32 itemSubclassMask) const; void SendKnownSpells(); void SendUnlearnSpells(); - bool AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, int32 fromSkill = 0); - void LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill = 0, bool suppressMessaging = false); + bool AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, int32 fromSkill = 0, Optional<int32> traitDefinitionId = {}); + void LearnSpell(uint32 spell_id, bool dependent, int32 fromSkill = 0, bool suppressMessaging = false, Optional<int32> traitDefinitionId = {}); void RemoveSpell(uint32 spell_id, bool disabled = false, bool learn_low_rank = true, bool suppressMessaging = false); void ResetSpells(bool myClassOnly = false); void LearnCustomSpells(); @@ -1869,8 +1877,23 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> 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; } + void StartLoadingActionButtons(std::function<void()>&& callback = nullptr); void LoadActions(PreparedQueryResult result); + // Traits + void CreateTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig); + void AddTraitConfig(WorldPackets::Traits::TraitConfig const& traitConfig); + UF::TraitConfig const* GetTraitConfig(int32 configId) const; + void UpdateTraitConfig(WorldPackets::Traits::TraitConfig&& newConfig, int32 savedConfigId, bool withCastTime); + void ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::TraitConfig const& newConfig, bool applyTraits, bool consumeCurrencies); + void RenameTraitConfig(int32 editedConfigId, std::string&& newName); + void DeleteTraitConfig(int32 deletedConfigId); + void ApplyTraitConfig(int32 configId, bool apply); + void ApplyTraitEntry(int32 traitNodeEntryId, int32 rank, int32 grantedRanks, bool apply); + void SetActiveCombatTraitConfigID(int32 traitConfigId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ActiveCombatTraitConfigID), traitConfigId); } + void SetTraitConfigUseStarterBuild(int32 traitConfigId, bool useStarterBuild); + void SetTraitConfigUseSharedActionBars(int32 traitConfigId, bool usesSharedActionBars, bool isLastSelectedSavedConfig); + uint32 GetFreePrimaryProfessionPoints() const { return m_activePlayerData->CharacterPoints; } void SetFreePrimaryProfessions(uint16 profs) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), profs); } void InitPrimaryProfessions(); @@ -2908,6 +2931,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _LoadGlyphs(PreparedQueryResult result); void _LoadTalents(PreparedQueryResult result); void _LoadPvpTalents(PreparedQueryResult result); + void _LoadTraits(PreparedQueryResult configsResult, PreparedQueryResult entriesResult); void _LoadInstanceTimeRestrictions(PreparedQueryResult result); void _LoadPetStable(uint32 summonedPetNumber, PreparedQueryResult result); void _LoadCurrency(PreparedQueryResult result); @@ -2935,6 +2959,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void _SaveBGData(CharacterDatabaseTransaction trans); void _SaveGlyphs(CharacterDatabaseTransaction trans) const; void _SaveTalents(CharacterDatabaseTransaction trans); + void _SaveTraits(CharacterDatabaseTransaction trans); void _SaveStats(CharacterDatabaseTransaction trans) const; void _SaveInstanceTimeRestrictions(CharacterDatabaseTransaction trans); void _SaveCurrency(CharacterDatabaseTransaction trans); @@ -3014,6 +3039,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> SpecializationInfo _specializationInfo; + std::unordered_map<int32, PlayerSpellState> m_traitConfigStates; + ActionButtonList m_actionButtons; float m_auraBaseFlatMod[BASEMOD_END]; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 98adc84d6fe..9a7d20cc1a6 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -176,10 +176,6 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_VOID_STORAGE, stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS); - stmt->setUInt64(0, lowGuid); - res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACTIONS, stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL); stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAILS, stmt); @@ -331,6 +327,14 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_GARRISON_FOLLOWER_ABILITIES, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_TRAIT_ENTRIES); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRAIT_ENTRIES, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_TRAIT_CONFIGS); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TRAIT_CONFIGS, stmt); + return res; } diff --git a/src/server/game/Handlers/TraitHandler.cpp b/src/server/game/Handlers/TraitHandler.cpp new file mode 100644 index 00000000000..ae61b159d62 --- /dev/null +++ b/src/server/game/Handlers/TraitHandler.cpp @@ -0,0 +1,253 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "WorldSession.h" +#include "Battleground.h" +#include "DB2Stores.h" +#include "Player.h" +#include "SpellHistory.h" +#include "TraitMgr.h" +#include "TraitPackets.h" + +void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig) +{ + int32 configId = traitsCommitConfig.Config.ID; + UF::TraitConfig const* existingConfig = _player->GetTraitConfig(configId); + if (!existingConfig) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); + return; + } + + if (_player->IsInCombat()) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_AFFECTING_COMBAT).Write()); + return; + } + + if (_player->GetBattleground() && _player->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_IN_PVP_MATCH).Write()); + return; + } + + auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry* + { + auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId; + }); + return entryItr != config.Entries.end() ? &*entryItr : nullptr; + }; + + bool hasRemovedEntries = false; + WorldPackets::Traits::TraitConfig newConfigState(*existingConfig); + for (WorldPackets::Traits::TraitEntry const& newEntry : traitsCommitConfig.Config.Entries) + { + WorldPackets::Traits::TraitEntry* traitEntry = findEntry(newConfigState, newEntry.TraitNodeID, newEntry.TraitNodeEntryID); + if (traitEntry && traitEntry->Rank > newEntry.Rank) + { + TraitNodeEntry const* traitNode = sTraitNodeStore.LookupEntry(newEntry.TraitNodeID); + if (!traitNode) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); + return; + } + + TraitTreeEntry const* traitTree = sTraitTreeStore.LookupEntry(traitNode->TraitTreeID); + if (!traitTree) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); + return; + } + + if (traitTree->GetFlags().HasFlag(TraitTreeFlag::CannotRefund)) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); + return; + } + + TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(newEntry.TraitNodeEntryID); + if (!traitNodeEntry) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); + return; + } + + TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID); + if (!traitDefinition) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write()); + return; + } + + if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID)) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); + return; + } + + if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID)) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write()); + return; + } + + hasRemovedEntries = true; + } + + if (traitEntry) + { + if (newEntry.Rank) + traitEntry->Rank = newEntry.Rank; + else + newConfigState.Entries.erase(std::remove_if(newConfigState.Entries.begin(), newConfigState.Entries.end(), [&newEntry](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID; + }), newConfigState.Entries.end()); + } + else + newConfigState.Entries.emplace_back() = newEntry; + } + + TalentLearnResult validationResult = TraitMgr::ValidateConfig(newConfigState, _player, true); + if (validationResult != TALENT_LEARN_OK) + { + SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, validationResult).Write()); + return; + } + + bool needsCastTime = newConfigState.Type == TraitConfigType::Combat && hasRemovedEntries; + + if (traitsCommitConfig.SavedLocalIdentifier) + newConfigState.LocalIdentifier = traitsCommitConfig.SavedLocalIdentifier; + else if (UF::TraitConfig const* savedConfig = _player->GetTraitConfig(traitsCommitConfig.SavedLocalIdentifier)) + newConfigState.LocalIdentifier = savedConfig->LocalIdentifier; + + _player->UpdateTraitConfig(std::move(newConfigState), traitsCommitConfig.SavedConfigID, needsCastTime); +} + +void WorldSession::HandleClassTalentsRequestNewConfig(WorldPackets::Traits::ClassTalentsRequestNewConfig& classTalentsRequestNewConfig) +{ + if (classTalentsRequestNewConfig.Config.Type != TraitConfigType::Combat) + return; + + if ((classTalentsRequestNewConfig.Config.CombatConfigFlags & TraitCombatConfigFlags::ActiveForSpec) != TraitCombatConfigFlags::None) + return; + + int64 configCount = std::count_if(_player->m_activePlayerData->TraitConfigs.begin(), _player->m_activePlayerData->TraitConfigs.end(), [](UF::TraitConfig const& traitConfig) + { + return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && (static_cast<TraitCombatConfigFlags>(*traitConfig.CombatConfigFlags) & TraitCombatConfigFlags::ActiveForSpec) == TraitCombatConfigFlags::None; + }); + if (configCount >= TraitMgr::MAX_COMBAT_TRAIT_CONFIGS) + return; + + auto findFreeLocalIdentifier = [&]() + { + int32 index = 1; + while (_player->m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& traitConfig) + { + return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && traitConfig.ChrSpecializationID == int32(_player->GetPrimarySpecialization()) + && traitConfig.LocalIdentifier == index; + }) >= 0) + ++index; + + return index; + }; + + classTalentsRequestNewConfig.Config.ChrSpecializationID = _player->GetPrimarySpecialization(); + classTalentsRequestNewConfig.Config.LocalIdentifier = findFreeLocalIdentifier(); + + for (UF::TraitEntry const& grantedEntry : TraitMgr::GetGrantedTraitEntriesForConfig(classTalentsRequestNewConfig.Config, _player)) + { + auto entryItr = std::find_if(classTalentsRequestNewConfig.Config.Entries.begin(), classTalentsRequestNewConfig.Config.Entries.end(), + [&](WorldPackets::Traits::TraitEntry const& entry) { return entry.TraitNodeID == grantedEntry.TraitNodeID && entry.TraitNodeEntryID == grantedEntry.TraitNodeEntryID; }); + + WorldPackets::Traits::TraitEntry& newEntry = entryItr != classTalentsRequestNewConfig.Config.Entries.end() ? *entryItr : classTalentsRequestNewConfig.Config.Entries.emplace_back(); + newEntry.TraitNodeID = grantedEntry.TraitNodeID; + newEntry.TraitNodeEntryID = grantedEntry.TraitNodeEntryID; + newEntry.Rank = grantedEntry.Rank; + newEntry.GrantedRanks = grantedEntry.GrantedRanks; + if (TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(grantedEntry.TraitNodeEntryID)) + if (newEntry.Rank + newEntry.GrantedRanks > traitNodeEntry->MaxRanks) + newEntry.Rank = std::max(0, traitNodeEntry->MaxRanks - newEntry.GrantedRanks); + } + + TalentLearnResult validationResult = TraitMgr::ValidateConfig(classTalentsRequestNewConfig.Config, _player); + if (validationResult != TALENT_LEARN_OK) + return; + + _player->CreateTraitConfig(classTalentsRequestNewConfig.Config); +} + +void WorldSession::HandleClassTalentsRenameConfig(WorldPackets::Traits::ClassTalentsRenameConfig& classTalentsRenameConfig) +{ + _player->RenameTraitConfig(classTalentsRenameConfig.ConfigID, classTalentsRenameConfig.Name.Move()); +} + +void WorldSession::HandleClassTalentsDeleteConfig(WorldPackets::Traits::ClassTalentsDeleteConfig const& classTalentsDeleteConfig) +{ + _player->DeleteTraitConfig(classTalentsDeleteConfig.ConfigID); +} + +void WorldSession::HandleClassTalentsSetStarterBuildActive(WorldPackets::Traits::ClassTalentsSetStarterBuildActive const& classTalentsSetStarterBuildActive) +{ + UF::TraitConfig const* traitConfig = _player->GetTraitConfig(classTalentsSetStarterBuildActive.ConfigID); + if (!traitConfig) + return; + + if (static_cast<TraitConfigType>(*traitConfig->Type) != TraitConfigType::Combat) + return; + + if (!EnumFlag(static_cast<TraitCombatConfigFlags>(*traitConfig->CombatConfigFlags)).HasFlag(TraitCombatConfigFlags::ActiveForSpec)) + return; + + if (classTalentsSetStarterBuildActive.Active) + { + WorldPackets::Traits::TraitConfig newConfigState(*traitConfig); + + auto findFreeLocalIdentifier = [&]() + { + int32 index = 1; + while (_player->m_activePlayerData->TraitConfigs.FindIndexIf([&](UF::TraitConfig const& traitConfig) + { + return static_cast<TraitConfigType>(*traitConfig.Type) == TraitConfigType::Combat + && traitConfig.ChrSpecializationID == int32(_player->GetPrimarySpecialization()) + && traitConfig.LocalIdentifier == index; + }) >= 0) + ++index; + + return index; + }; + + TraitMgr::InitializeStarterBuildTraitConfig(newConfigState, _player); + newConfigState.CombatConfigFlags |= TraitCombatConfigFlags::StarterBuild; + newConfigState.LocalIdentifier = findFreeLocalIdentifier(); + + _player->UpdateTraitConfig(std::move(newConfigState), 0, true); + } + else + _player->SetTraitConfigUseStarterBuild(classTalentsSetStarterBuildActive.ConfigID, false); +} + +void WorldSession::HandleClassTalentsSetUsesSharedActionBars(WorldPackets::Traits::ClassTalentsSetUsesSharedActionBars const& classTalentsSetUsesSharedActionBars) +{ + _player->SetTraitConfigUseSharedActionBars(classTalentsSetUsesSharedActionBars.ConfigID, classTalentsSetUsesSharedActionBars.UsesShared, + classTalentsSetUsesSharedActionBars.IsLastSelectedSavedConfig); +} diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index eab189b56e9..a9658aec4eb 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -77,6 +77,7 @@ #include "TotemPackets.h" #include "ToyPackets.h" #include "TradePackets.h" +#include "TraitPackets.h" #include "TransmogrificationPackets.h" #include "VehiclePackets.h" #include "VoidStoragePackets.h" diff --git a/src/server/game/Server/Packets/PacketUtilities.h b/src/server/game/Server/Packets/PacketUtilities.h index f445169784c..4af2d2c4b17 100644 --- a/src/server/game/Server/Packets/PacketUtilities.h +++ b/src/server/game/Server/Packets/PacketUtilities.h @@ -198,6 +198,16 @@ namespace WorldPackets return _storage.back(); } + iterator erase(const_iterator first, const_iterator last) + { + return _storage.erase(first, last); + } + + void clear() + { + _storage.clear(); + } + private: storage_type _storage; }; diff --git a/src/server/game/Server/Packets/TraitPackets.cpp b/src/server/game/Server/Packets/TraitPackets.cpp new file mode 100644 index 00000000000..2c1770f6aaa --- /dev/null +++ b/src/server/game/Server/Packets/TraitPackets.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TraitPackets.h" + +namespace WorldPackets::Traits +{ +void TraitsCommitConfig::Read() +{ + _worldPacket >> Config; + _worldPacket >> SavedConfigID; + _worldPacket >> SavedLocalIdentifier; +} + +WorldPacket const* TraitConfigCommitFailed::Write() +{ + _worldPacket << int32(ConfigID); + _worldPacket << int32(SpellID); + _worldPacket.WriteBits(Reason, 4); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +void ClassTalentsRequestNewConfig::Read() +{ + _worldPacket >> Config; +} + +void ClassTalentsRenameConfig::Read() +{ + _worldPacket >> ConfigID; + uint32 nameLength = _worldPacket.ReadBits(9); + Name = _worldPacket.ReadString(nameLength, false); +} + +void ClassTalentsDeleteConfig::Read() +{ + _worldPacket >> ConfigID; +} + +void ClassTalentsSetStarterBuildActive::Read() +{ + _worldPacket >> ConfigID; + Active = _worldPacket.ReadBit(); +} + +void ClassTalentsSetUsesSharedActionBars::Read() +{ + _worldPacket >> ConfigID; + UsesShared = _worldPacket.ReadBit(); + IsLastSelectedSavedConfig = _worldPacket.ReadBit(); +} +} diff --git a/src/server/game/Server/Packets/TraitPackets.h b/src/server/game/Server/Packets/TraitPackets.h new file mode 100644 index 00000000000..88c2ae43637 --- /dev/null +++ b/src/server/game/Server/Packets/TraitPackets.h @@ -0,0 +1,106 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_TRAIT_PACKETS_H +#define TRINITYCORE_TRAIT_PACKETS_H + +#include "Packet.h" +#include "TraitPacketsCommon.h" + +namespace WorldPackets::Traits +{ +class TraitsCommitConfig final : public ClientPacket +{ +public: + explicit TraitsCommitConfig(WorldPacket&& packet) : ClientPacket(CMSG_TRAITS_COMMIT_CONFIG, std::move(packet)) { } + + void Read() override; + + TraitConfig Config; + int32 SavedConfigID = 0; + int32 SavedLocalIdentifier = 0; +}; + +class TraitConfigCommitFailed final : public ServerPacket +{ +public: + TraitConfigCommitFailed(int32 configId = 0, int32 spellId = 0, int32 reason = 0) : ServerPacket(SMSG_TRAIT_CONFIG_COMMIT_FAILED, 4 + 4 + 1), + ConfigID(configId), SpellID(spellId), Reason(reason) { } + + WorldPacket const* Write() override; + + int32 ConfigID; + int32 SpellID; + int32 Reason; +}; + +class ClassTalentsRequestNewConfig final : public ClientPacket +{ +public: + explicit ClassTalentsRequestNewConfig(WorldPacket&& packet) : ClientPacket(CMSG_CLASS_TALENTS_REQUEST_NEW_CONFIG, std::move(packet)) { } + + void Read() override; + + TraitConfig Config; +}; + +class ClassTalentsRenameConfig final : public ClientPacket +{ +public: + explicit ClassTalentsRenameConfig(WorldPacket&& packet) : ClientPacket(CMSG_CLASS_TALENTS_RENAME_CONFIG, std::move(packet)) { } + + void Read() override; + + int32 ConfigID = 0; + String<259> Name; +}; + +class ClassTalentsDeleteConfig final : public ClientPacket +{ +public: + explicit ClassTalentsDeleteConfig(WorldPacket&& packet) : ClientPacket(CMSG_CLASS_TALENTS_DELETE_CONFIG, std::move(packet)) { } + + void Read() override; + + int32 ConfigID = 0; +}; + +class ClassTalentsSetStarterBuildActive final : public ClientPacket +{ +public: + explicit ClassTalentsSetStarterBuildActive(WorldPacket&& packet) : ClientPacket(CMSG_CLASS_TALENTS_SET_STARTER_BUILD_ACTIVE, std::move(packet)) { } + + void Read() override; + + int32 ConfigID = 0; + bool Active = false; +}; + +class ClassTalentsSetUsesSharedActionBars final : public ClientPacket +{ +public: + explicit ClassTalentsSetUsesSharedActionBars(WorldPacket&& packet) : ClientPacket(CMSG_CLASS_TALENTS_SET_USES_SHARED_ACTION_BARS, std::move(packet)) { } + + void Read() override; + + int32 ConfigID = 0; + bool UsesShared = false; + bool IsLastSelectedSavedConfig = false; +}; +} + +#endif // TRINITYCORE_TRAIT_PACKETS_H diff --git a/src/server/game/Server/Packets/TraitPacketsCommon.cpp b/src/server/game/Server/Packets/TraitPacketsCommon.cpp index 482f3b5e91d..d6bfe6667cf 100644 --- a/src/server/game/Server/Packets/TraitPacketsCommon.cpp +++ b/src/server/game/Server/Packets/TraitPacketsCommon.cpp @@ -17,9 +17,46 @@ #include "TraitPacketsCommon.h" #include "DBCEnums.h" +#include "UpdateFields.h" namespace WorldPackets::Traits { +TraitEntry::TraitEntry() = default; + +TraitEntry::TraitEntry(UF::TraitEntry const& ufEntry) +{ + TraitNodeID = ufEntry.TraitNodeID; + TraitNodeEntryID = ufEntry.TraitNodeEntryID; + Rank = ufEntry.Rank; + GrantedRanks = ufEntry.GrantedRanks; +} + +TraitConfig::TraitConfig() = default; + +TraitConfig::TraitConfig(UF::TraitConfig const& ufConfig) +{ + ID = ufConfig.ID; + Type = static_cast<TraitConfigType>(*ufConfig.Type); + ChrSpecializationID = ufConfig.ChrSpecializationID; + CombatConfigFlags = static_cast<TraitCombatConfigFlags>(*ufConfig.CombatConfigFlags); + LocalIdentifier = ufConfig.LocalIdentifier; + SkillLineID = ufConfig.SkillLineID; + TraitSystemID = ufConfig.TraitSystemID; + for (UF::TraitEntry const& ufEntry : ufConfig.Entries) + Entries.emplace_back(ufEntry); + Name = ufConfig.Name; +} + +ByteBuffer& operator>>(ByteBuffer& data, TraitEntry& traitEntry) +{ + data >> traitEntry.TraitNodeID; + data >> traitEntry.TraitNodeEntryID; + data >> traitEntry.Rank; + data >> traitEntry.GrantedRanks; + + return data; +} + ByteBuffer& operator<<(ByteBuffer& data, TraitEntry const& traitEntry) { data << int32(traitEntry.TraitNodeID); @@ -30,6 +67,37 @@ ByteBuffer& operator<<(ByteBuffer& data, TraitEntry const& traitEntry) return data; } +ByteBuffer& operator>>(ByteBuffer& data, TraitConfig& traitConfig) +{ + data >> traitConfig.ID; + traitConfig.Type = data.read<TraitConfigType, int32>(); + traitConfig.Entries.resize(data.read<uint32>()); + switch (traitConfig.Type) + { + case TraitConfigType::Combat: + data >> traitConfig.ChrSpecializationID; + traitConfig.CombatConfigFlags = data.read<TraitCombatConfigFlags, int32>(); + data >> traitConfig.LocalIdentifier; + break; + case TraitConfigType::Profession: + data >> traitConfig.SkillLineID; + break; + case TraitConfigType::Generic: + data >> traitConfig.TraitSystemID; + break; + default: + break; + } + + for (TraitEntry& traitEntry : traitConfig.Entries) + data >> traitEntry; + + uint32 nameLength = data.ReadBits(9); + traitConfig.Name = data.ReadString(nameLength, false); + + return data; +} + ByteBuffer& operator<<(ByteBuffer& data, TraitConfig const& traitConfig) { data << int32(traitConfig.ID); diff --git a/src/server/game/Server/Packets/TraitPacketsCommon.h b/src/server/game/Server/Packets/TraitPacketsCommon.h index d61e16ed49b..96186412362 100644 --- a/src/server/game/Server/Packets/TraitPacketsCommon.h +++ b/src/server/game/Server/Packets/TraitPacketsCommon.h @@ -23,10 +23,19 @@ enum class TraitCombatConfigFlags : int32; enum class TraitConfigType : int32; +namespace UF +{ +struct TraitConfig; +struct TraitEntry; +} + namespace WorldPackets::Traits { struct TraitEntry { + TraitEntry(); + explicit TraitEntry(UF::TraitEntry const& ufEntry); + int32 TraitNodeID = 0; int32 TraitNodeEntryID = 0; int32 Rank = 0; @@ -35,6 +44,9 @@ struct TraitEntry struct TraitConfig { + TraitConfig(); + explicit TraitConfig(UF::TraitConfig const& ufConfig); + int32 ID = 0; TraitConfigType Type = {}; int32 ChrSpecializationID = 0; @@ -46,7 +58,9 @@ struct TraitConfig String<259> Name; }; +ByteBuffer& operator>>(ByteBuffer& data, TraitEntry& traitEntry); ByteBuffer& operator<<(ByteBuffer& data, TraitEntry const& traitEntry); +ByteBuffer& operator>>(ByteBuffer& data, TraitConfig& traitConfig); ByteBuffer& operator<<(ByteBuffer& data, TraitConfig const& traitConfig); } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 8e197c8b98b..5df4f540e4e 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -329,13 +329,13 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_CHOICE_RESPONSE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePlayerChoiceResponse); DEFINE_HANDLER(CMSG_CHROMIE_TIME_SELECT_EXPANSION, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_CLAIM_WEEKLY_REWARD, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CLASS_TALENTS_DELETE_CONFIG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_CLASS_TALENTS_DELETE_CONFIG, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleClassTalentsDeleteConfig); DEFINE_HANDLER(CMSG_CLASS_TALENTS_NOTIFY_EMPTY_CONFIG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_CLASS_TALENTS_NOTIFY_VALIDATION_FAILED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CLASS_TALENTS_RENAME_CONFIG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CLASS_TALENTS_REQUEST_NEW_CONFIG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CLASS_TALENTS_SET_STARTER_BUILD_ACTIVE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); - DEFINE_HANDLER(CMSG_CLASS_TALENTS_SET_USES_SHARED_ACTION_BARS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_CLASS_TALENTS_RENAME_CONFIG, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleClassTalentsRenameConfig); + DEFINE_HANDLER(CMSG_CLASS_TALENTS_REQUEST_NEW_CONFIG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleClassTalentsRequestNewConfig); + DEFINE_HANDLER(CMSG_CLASS_TALENTS_SET_STARTER_BUILD_ACTIVE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleClassTalentsSetStarterBuildActive); + DEFINE_HANDLER(CMSG_CLASS_TALENTS_SET_USES_SHARED_ACTION_BARS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleClassTalentsSetUsesSharedActionBars); DEFINE_HANDLER(CMSG_CLEAR_NEW_APPEARANCE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_CLEAR_RAID_MARKER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleClearRaidMarker); DEFINE_HANDLER(CMSG_CLEAR_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleClearTradeItemOpcode); @@ -931,7 +931,7 @@ void OpcodeTable::Initialize() DEFINE_HANDLER(CMSG_TRADE_SKILL_SET_FAVORITE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_TRAINER_BUY_SPELL, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTrainerBuySpellOpcode); DEFINE_HANDLER(CMSG_TRAINER_LIST, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTrainerListOpcode); - DEFINE_HANDLER(CMSG_TRAITS_COMMIT_CONFIG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_TRAITS_COMMIT_CONFIG, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTraitsCommitConfig); DEFINE_HANDLER(CMSG_TRAITS_TALENT_TEST_UNLEARN_SPELLS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_TRANSMOGRIFY_ITEMS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTransmogrifyItems); DEFINE_HANDLER(CMSG_TURN_IN_PETITION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTurnInPetition); @@ -2070,7 +2070,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRADE_UPDATED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRAINER_BUY_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRAINER_LIST, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRAIT_CONFIG_COMMIT_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRAIT_CONFIG_COMMIT_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSFER_ABORTED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRANSFER_PENDING, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_TREASURE_PICKER_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 6ff16503847..b26204a0989 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -773,6 +773,16 @@ namespace WorldPackets class TradeStatus; } + namespace Traits + { + class TraitsCommitConfig; + class ClassTalentsRequestNewConfig; + class ClassTalentsRenameConfig; + class ClassTalentsDeleteConfig; + class ClassTalentsSetStarterBuildActive; + class ClassTalentsSetUsesSharedActionBars; + } + namespace Transmogrification { class TransmogrifyItems; @@ -1497,6 +1507,13 @@ class TC_GAME_API WorldSession void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe); void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet); + void HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig); + void HandleClassTalentsRequestNewConfig(WorldPackets::Traits::ClassTalentsRequestNewConfig& classTalentsRequestNewConfig); + void HandleClassTalentsRenameConfig(WorldPackets::Traits::ClassTalentsRenameConfig& classTalentsRenameConfig); + void HandleClassTalentsDeleteConfig(WorldPackets::Traits::ClassTalentsDeleteConfig const& classTalentsDeleteConfig); + void HandleClassTalentsSetStarterBuildActive(WorldPackets::Traits::ClassTalentsSetStarterBuildActive const& classTalentsSetStarterBuildActive); + void HandleClassTalentsSetUsesSharedActionBars(WorldPackets::Traits::ClassTalentsSetUsesSharedActionBars const& classTalentsSetUsesSharedActionBars); + void HandleQuestgiverStatusQueryOpcode(WorldPackets::Quest::QuestGiverStatusQuery& packet); void HandleQuestgiverStatusMultipleQuery(WorldPackets::Quest::QuestGiverStatusMultipleQuery& packet); void HandleQuestgiverHelloOpcode(WorldPackets::Quest::QuestGiverHello& packet); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 2cd10e0b4e8..58007c844ac 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -55,6 +55,7 @@ #include "SpellScript.h" #include "TemporarySummon.h" #include "TradeData.h" +#include "TraitPackets.h" #include "Util.h" #include "VMapFactory.h" #include "Vehicle.h" @@ -4222,7 +4223,14 @@ void Spell::finish(bool ok) Unit::ProcSkillsAndAuras(unitCaster, nullptr, PROC_FLAG_CAST_ENDED, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, this, nullptr, nullptr); if (!ok) + { + // on failure (or manual cancel) send TraitConfigCommitFailed to revert talent UI saved config selection + if (m_caster->IsPlayer() && m_spellInfo->HasEffect(SPELL_EFFECT_CHANGE_ACTIVE_COMBAT_TRAIT_CONFIG)) + if (WorldPackets::Traits::TraitConfig const* traitConfig = std::any_cast<WorldPackets::Traits::TraitConfig>(&m_customArg)) + m_caster->ToPlayer()->SendDirectMessage(WorldPackets::Traits::TraitConfigCommitFailed(traitConfig->ID).Write()); + return; + } if (unitCaster->GetTypeId() == TYPEID_UNIT && unitCaster->IsSummon()) { diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 41b8b99160c..1ee8a54a5c3 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -408,6 +408,8 @@ class TC_GAME_API Spell void EffectModifyCooldowns(); void EffectModifyCooldownsByCategory(); void EffectModifySpellCharges(); + void EffectCreateTraitTreeConfig(); + void EffectChangeActiveCombatTraitConfig(); typedef std::unordered_set<Aura*> UsedSpellMods; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 2bde0329e6f..bc768ab550b 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -75,6 +75,8 @@ #include "TalentPackets.h" #include "TemporarySummon.h" #include "Totem.h" +#include "TraitMgr.h" +#include "TraitPacketsCommon.h" #include "Unit.h" #include "Util.h" #include "World.h" @@ -386,8 +388,8 @@ NonDefaultConstructible<SpellEffectHandlerFn> SpellEffectHandlers[TOTAL_SPELL_EF &Spell::EffectUnused, //300 SPELL_EFFECT_300 &Spell::EffectNULL, //301 SPELL_EFFECT_CRAFT_ENCHANT &Spell::EffectNULL, //302 SPELL_EFFECT_GATHERING - &Spell::EffectNULL, //303 SPELL_EFFECT_CREATE_TRAIT_TREE_CONFIG - &Spell::EffectNULL, //304 SPELL_EFFECT_CHANGE_ACTIVE_COMBAT_TRAIT_CONFIG + &Spell::EffectCreateTraitTreeConfig, //303 SPELL_EFFECT_CREATE_TRAIT_TREE_CONFIG + &Spell::EffectChangeActiveCombatTraitConfig, //304 SPELL_EFFECT_CHANGE_ACTIVE_COMBAT_TRAIT_CONFIG }; void Spell::EffectNULL() @@ -5873,3 +5875,37 @@ void Spell::EffectModifySpellCharges() for (int32 i = 0; i < damage; ++i) unitTarget->GetSpellHistory()->RestoreCharge(effectInfo->MiscValue); } + +void Spell::EffectCreateTraitTreeConfig() +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + Player* target = Object::ToPlayer(unitTarget); + if (!target) + return; + + WorldPackets::Traits::TraitConfig newConfig; + newConfig.Type = TraitMgr::GetConfigTypeForTree(effectInfo->MiscValue); + if (newConfig.Type != TraitConfigType::Generic) + return; + + newConfig.TraitSystemID = sTraitTreeStore.AssertEntry(effectInfo->MiscValue)->TraitSystemID; + target->CreateTraitConfig(newConfig); +} + +void Spell::EffectChangeActiveCombatTraitConfig() +{ + if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + return; + + Player* target = Object::ToPlayer(unitTarget); + if (!target) + return; + + WorldPackets::Traits::TraitConfig* traitConfig = std::any_cast<WorldPackets::Traits::TraitConfig>(&m_customArg); + if (!traitConfig) + return; + + target->UpdateTraitConfig(std::move(*traitConfig), damage, false); +} diff --git a/src/server/game/Spells/TraitMgr.cpp b/src/server/game/Spells/TraitMgr.cpp new file mode 100644 index 00000000000..33d5f215f2c --- /dev/null +++ b/src/server/game/Spells/TraitMgr.cpp @@ -0,0 +1,735 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TraitMgr.h" +#include "Containers.h" +#include "DB2Stores.h" +#include "IteratorPair.h" +#include "Player.h" +#include "TraitPacketsCommon.h" + +namespace TraitMgr +{ +namespace +{ +struct NodeEntry; +struct Node; +struct NodeGroup; +struct Tree; + +struct NodeEntry +{ + TraitNodeEntryEntry const* Data = nullptr; + std::vector<TraitCondEntry const*> Conditions; + std::vector<TraitCostEntry const*> Costs; +}; + +struct Node +{ + TraitNodeEntry const* Data = nullptr; + std::vector<NodeEntry> Entries; + std::vector<NodeGroup const*> Groups; + std::vector<std::pair<Node const*, TraitEdgeType>> ParentNodes; // TraitEdge::LeftTraitNodeID + std::vector<TraitCondEntry const*> Conditions; + std::vector<TraitCostEntry const*> Costs; +}; + +struct NodeGroup +{ + TraitNodeGroupEntry const* Data = nullptr; + std::vector<TraitCondEntry const*> Conditions; + std::vector<TraitCostEntry const*> Costs; + std::vector<Node const*> Nodes; +}; + +struct Tree +{ + TraitTreeEntry const* Data = nullptr; + std::vector<Node const*> Nodes; + std::vector<TraitCostEntry const*> Costs; + std::vector<TraitCurrencyEntry const*> Currencies; + TraitConfigType ConfigType = TraitConfigType::Invalid; +}; + +std::unordered_map<int32, NodeGroup> _traitGroups; +std::unordered_map<int32, Node> _traitNodes; +std::unordered_map<int32, Tree> _traitTrees; +std::array<int32, MAX_CLASSES> _skillLinesByClass; +std::unordered_map<int32, std::vector<Tree const*>> _traitTreesBySkillLine; +std::unordered_map<int32, std::vector<Tree const*>> _traitTreesByTraitSystem; +int32 _configIdGenerator = 0; +std::unordered_map<int32, std::vector<TraitCurrencySourceEntry const*>> _traitCurrencySourcesByCurrency; +std::unordered_map<int32, std::vector<TraitDefinitionEffectPointsEntry const*>> _traitDefinitionEffectPointModifiers; +std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> _traitTreeLoadoutsByChrSpecialization; +} + +void Load() +{ + _configIdGenerator = time(nullptr); + + std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeEntryConditions; + for (TraitNodeEntryXTraitCondEntry const* traitNodeEntryXTraitCondEntry : sTraitNodeEntryXTraitCondStore) + if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeEntryXTraitCondEntry->TraitCondID)) + nodeEntryConditions[traitNodeEntryXTraitCondEntry->TraitNodeEntryID].push_back(traitCondEntry); + + std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeEntryCosts; + for (TraitNodeEntryXTraitCostEntry const* traitNodeEntryXTraitCostEntry : sTraitNodeEntryXTraitCostStore) + if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeEntryXTraitCostEntry->TraitCostID)) + nodeEntryCosts[traitNodeEntryXTraitCostEntry->TraitNodeEntryID].push_back(traitCostEntry); + + std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeGroupConditions; + for (TraitNodeGroupXTraitCondEntry const* traitNodeGroupXTraitCondEntry : sTraitNodeGroupXTraitCondStore) + if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeGroupXTraitCondEntry->TraitCondID)) + nodeGroupConditions[traitNodeGroupXTraitCondEntry->TraitNodeGroupID].push_back(traitCondEntry); + + std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeGroupCosts; + for (TraitNodeGroupXTraitCostEntry const* traitNodeGroupXTraitCostEntry : sTraitNodeGroupXTraitCostStore) + if (TraitCostEntry const* traitCondEntry = sTraitCostStore.LookupEntry(traitNodeGroupXTraitCostEntry->TraitCostID)) + nodeGroupCosts[traitNodeGroupXTraitCostEntry->TraitNodeGroupID].push_back(traitCondEntry); + + std::unordered_multimap<int32, int32> nodeGroups; + for (TraitNodeGroupXTraitNodeEntry const* traitNodeGroupXTraitNodeEntry : sTraitNodeGroupXTraitNodeStore) + nodeGroups.emplace(traitNodeGroupXTraitNodeEntry->TraitNodeID, traitNodeGroupXTraitNodeEntry->TraitNodeGroupID); + + std::unordered_map<int32, std::vector<TraitCondEntry const*>> nodeConditions; + for (TraitNodeXTraitCondEntry const* traitNodeXTraitCondEntry : sTraitNodeXTraitCondStore) + if (TraitCondEntry const* traitCondEntry = sTraitCondStore.LookupEntry(traitNodeXTraitCondEntry->TraitCondID)) + nodeConditions[traitNodeXTraitCondEntry->TraitNodeID].push_back(traitCondEntry); + + std::unordered_map<int32, std::vector<TraitCostEntry const*>> nodeCosts; + for (TraitNodeXTraitCostEntry const* traitNodeXTraitCostEntry : sTraitNodeXTraitCostStore) + if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitNodeXTraitCostEntry->TraitCostID)) + nodeCosts[traitNodeXTraitCostEntry->TraitNodeID].push_back(traitCostEntry); + + std::unordered_multimap<int32, TraitNodeEntryEntry const*> nodeEntries; + for (TraitNodeXTraitNodeEntryEntry const* traitNodeXTraitNodeEntryEntry : sTraitNodeXTraitNodeEntryStore) + if (TraitNodeEntryEntry const* traitNodeEntryEntry = sTraitNodeEntryStore.LookupEntry(traitNodeXTraitNodeEntryEntry->TraitNodeEntryID)) + nodeEntries.emplace(traitNodeXTraitNodeEntryEntry->TraitNodeID, traitNodeEntryEntry); + + std::unordered_map<int32, std::vector<TraitCostEntry const*>> treeCosts; + for (TraitTreeXTraitCostEntry const* traitTreeXTraitCostEntry : sTraitTreeXTraitCostStore) + if (TraitCostEntry const* traitCostEntry = sTraitCostStore.LookupEntry(traitTreeXTraitCostEntry->TraitCostID)) + treeCosts[traitTreeXTraitCostEntry->TraitTreeID].push_back(traitCostEntry); + + std::unordered_map<int32, std::vector<TraitCurrencyEntry const*>> treeCurrencies; + for (TraitTreeXTraitCurrencyEntry const* traitTreeXTraitCurrencyEntry : sTraitTreeXTraitCurrencyStore) + if (TraitCurrencyEntry const* traitCurrencyEntry = sTraitCurrencyStore.LookupEntry(traitTreeXTraitCurrencyEntry->TraitCurrencyID)) + treeCurrencies[traitTreeXTraitCurrencyEntry->TraitTreeID].push_back(traitCurrencyEntry); + + std::unordered_map<int32, std::vector<int32>> traitTreesIdsByTraitSystem; + + for (TraitTreeEntry const* traitTree : sTraitTreeStore) + { + Tree& tree = _traitTrees[traitTree->ID]; + tree.Data = traitTree; + + if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(treeCosts, traitTree->ID)) + tree.Costs = std::move(*costs); + + if (std::vector<TraitCurrencyEntry const*>* currencies = Trinity::Containers::MapGetValuePtr(treeCurrencies, traitTree->ID)) + tree.Currencies = std::move(*currencies); + + if (traitTree->TraitSystemID) + { + traitTreesIdsByTraitSystem[traitTree->TraitSystemID].push_back(traitTree->ID); + tree.ConfigType = TraitConfigType::Generic; + } + } + + for (TraitNodeGroupEntry const* traitNodeGroup : sTraitNodeGroupStore) + { + NodeGroup& nodeGroup = _traitGroups[traitNodeGroup->ID]; + nodeGroup.Data = traitNodeGroup; + + if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeGroupConditions, traitNodeGroup->ID)) + nodeGroup.Conditions = std::move(*conditions); + + if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeGroupCosts, traitNodeGroup->ID)) + nodeGroup.Costs = std::move(*costs); + } + + for (TraitNodeEntry const* traitNode : sTraitNodeStore) + { + Node& node = _traitNodes[traitNode->ID]; + node.Data = traitNode; + + if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitNode->TraitTreeID)) + tree->Nodes.push_back(&node); + + for (auto&& [_, traitNodeEntry] : Trinity::Containers::MapEqualRange(nodeEntries, traitNode->ID)) + { + NodeEntry& entry = node.Entries.emplace_back(); + entry.Data = traitNodeEntry; + + if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeEntryConditions, traitNodeEntry->ID)) + entry.Conditions = std::move(*conditions); + + if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeEntryCosts, traitNodeEntry->ID)) + entry.Costs = std::move(*costs); + } + + for (auto&& [_, nodeGroupId] : Trinity::Containers::MapEqualRange(nodeGroups, traitNode->ID)) + { + NodeGroup* nodeGroup = Trinity::Containers::MapGetValuePtr(_traitGroups, nodeGroupId); + if (!nodeGroup) + continue; + + nodeGroup->Nodes.push_back(&node); + node.Groups.push_back(nodeGroup); + } + + if (std::vector<TraitCondEntry const*>* conditions = Trinity::Containers::MapGetValuePtr(nodeConditions, traitNode->ID)) + node.Conditions = std::move(*conditions); + + if (std::vector<TraitCostEntry const*>* costs = Trinity::Containers::MapGetValuePtr(nodeCosts, traitNode->ID)) + node.Costs = std::move(*costs); + } + + for (TraitEdgeEntry const* traitEdgeEntry : sTraitEdgeStore) + { + Node* left = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->LeftTraitNodeID); + Node* right = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEdgeEntry->RightTraitNodeID); + if (!left || !right) + continue; + + right->ParentNodes.emplace_back(left, static_cast<TraitEdgeType>(traitEdgeEntry->Type)); + } + + for (SkillLineXTraitTreeEntry const* skillLineXTraitTreeEntry : sSkillLineXTraitTreeStore) + { + Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, skillLineXTraitTreeEntry->TraitTreeID); + if (!tree) + continue; + + SkillLineEntry const* skillLineEntry = sSkillLineStore.LookupEntry(skillLineXTraitTreeEntry->SkillLineID); + if (!skillLineEntry) + continue; + + _traitTreesBySkillLine[skillLineXTraitTreeEntry->SkillLineID].push_back(tree); + if (skillLineEntry->CategoryID == SKILL_CATEGORY_CLASS) + { + for (SkillRaceClassInfoEntry const* skillRaceClassInfo : sDB2Manager.GetSkillRaceClassInfo(skillLineEntry->ID)) + for (int32 i = 1; i < MAX_CLASSES; ++i) + if (skillRaceClassInfo->ClassMask & (1 << (i - 1))) + _skillLinesByClass[i] = skillLineXTraitTreeEntry->SkillLineID; + + tree->ConfigType = TraitConfigType::Combat; + } + else + tree->ConfigType = TraitConfigType::Profession; + } + + for (auto&& [traitSystemId, traitTreeIds] : traitTreesIdsByTraitSystem) + for (int32 traitTreeId : traitTreeIds) + if (Tree* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId)) + _traitTreesByTraitSystem[traitSystemId].push_back(tree); + + for (TraitCurrencySourceEntry const* traitCurrencySource : sTraitCurrencySourceStore) + _traitCurrencySourcesByCurrency[traitCurrencySource->TraitCurrencyID].push_back(traitCurrencySource); + + for (TraitDefinitionEffectPointsEntry const* traitDefinitionEffectPoints : sTraitDefinitionEffectPointsStore) + _traitDefinitionEffectPointModifiers[traitDefinitionEffectPoints->TraitDefinitionID].push_back(traitDefinitionEffectPoints); + + std::unordered_map<int32, std::vector<TraitTreeLoadoutEntryEntry const*>> traitTreeLoadoutEntries; + for (TraitTreeLoadoutEntryEntry const* traitTreeLoadoutEntry : sTraitTreeLoadoutEntryStore) + traitTreeLoadoutEntries[traitTreeLoadoutEntry->TraitTreeLoadoutID].push_back(traitTreeLoadoutEntry); + + for (TraitTreeLoadoutEntry const* traitTreeLoadout : sTraitTreeLoadoutStore) + { + if (std::vector<TraitTreeLoadoutEntryEntry const*>* entries = Trinity::Containers::MapGetValuePtr(traitTreeLoadoutEntries, traitTreeLoadout->ID)) + { + std::sort(entries->begin(), entries->end(), [](TraitTreeLoadoutEntryEntry const* left, TraitTreeLoadoutEntryEntry const* right) + { + return left->OrderIndex < right->OrderIndex; + }); + // there should be only one loadout per spec, we take last one encountered + _traitTreeLoadoutsByChrSpecialization[traitTreeLoadout->ChrSpecializationID] = std::move(*entries); + } + } +} + +/** + * Generates new TraitConfig identifier. + * Because this only needs to be unique for each character we let it overflow +*/ +int32 GenerateNewTraitConfigId() +{ + if (_configIdGenerator == std::numeric_limits<int32>::max()) + _configIdGenerator = 0; + + return ++_configIdGenerator; +} + +TraitConfigType GetConfigTypeForTree(int32 traitTreeId) +{ + Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, traitTreeId); + if (!tree) + return TraitConfigType::Invalid; + + return tree->ConfigType; +} + +/** + * @brief Finds relevant TraitTree identifiers + * @param traitConfig config data + * @return Trait tree data +*/ +std::vector<Tree const*> const* GetTreesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig) +{ + switch (traitConfig.Type) + { + case TraitConfigType::Combat: + if (ChrSpecializationEntry const* chrSpecializationEntry = sChrSpecializationStore.LookupEntry(traitConfig.ChrSpecializationID)) + return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, _skillLinesByClass[chrSpecializationEntry->ClassID]); + break; + case TraitConfigType::Profession: + return Trinity::Containers::MapGetValuePtr(_traitTreesBySkillLine, traitConfig.SkillLineID); + case TraitConfigType::Generic: + return Trinity::Containers::MapGetValuePtr(_traitTreesByTraitSystem, traitConfig.TraitSystemID); + default: + break; + } + + return nullptr; +} + +bool HasEnoughCurrency(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32> const& currencies) +{ + auto getCurrencyCount = [&](int32 currencyId) + { + int32 const* count = Trinity::Containers::MapGetValuePtr(currencies, currencyId); + return count ? *count : 0; + }; + + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID); + for (NodeGroup const* group : node->Groups) + for (TraitCostEntry const* cost : group->Costs) + if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank) + return false; + + auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; }); + if (nodeEntryItr != node->Entries.end()) + for (TraitCostEntry const* cost : nodeEntryItr->Costs) + if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank) + return false; + + for (TraitCostEntry const* cost : node->Costs) + if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank) + return false; + + if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID)) + for (TraitCostEntry const* cost : tree->Costs) + if (getCurrencyCount(cost->TraitCurrencyID) < cost->Amount * entry.Rank) + return false; + + return true; +} + +void TakeCurrencyCost(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& currencies) +{ + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID); + for (NodeGroup const* group : node->Groups) + for (TraitCostEntry const* cost : group->Costs) + currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank; + + auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; }); + if (nodeEntryItr != node->Entries.end()) + for (TraitCostEntry const* cost : nodeEntryItr->Costs) + currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank; + + for (TraitCostEntry const* cost : node->Costs) + currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank; + + if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID)) + for (TraitCostEntry const* cost : tree->Costs) + currencies[cost->TraitCurrencyID] -= cost->Amount * entry.Rank; +} + +void FillOwnedCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, std::map<int32, int32>& currencies) +{ + std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig); + if (!trees) + return; + + auto hasTraitNodeEntry = [&traitConfig](int32 traitNodeEntryId) + { + return std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeEntryId](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0); + }) != traitConfig.Entries.end(); + }; + + for (Tree const* tree : *trees) + { + for (TraitCurrencyEntry const* currency : tree->Currencies) + { + switch (currency->GetType()) + { + case TraitCurrencyType::Gold: + { + int32& amount = currencies[currency->ID]; + if (player->GetMoney() > uint64(std::numeric_limits<int32>::max() - amount)) + amount = std::numeric_limits<int32>::max(); + else + amount += player->GetMoney(); + break; + } + case TraitCurrencyType::CurrencyTypesBased: + currencies[currency->ID] += player->GetCurrency(currency->CurrencyTypesID); + break; + case TraitCurrencyType::TraitSourced: + if (std::vector<TraitCurrencySourceEntry const*>* currencySources = Trinity::Containers::MapGetValuePtr(_traitCurrencySourcesByCurrency, currency->ID)) + { + for (TraitCurrencySourceEntry const* currencySource : *currencySources) + { + if (currencySource->QuestID && !player->IsQuestRewarded(currencySource->QuestID)) + continue; + + if (currencySource->AchievementID && !player->HasAchieved(currencySource->AchievementID)) + continue; + + if (currencySource->PlayerLevel && player->GetLevel() < currencySource->PlayerLevel) + continue; + + if (currencySource->TraitNodeEntryID && !hasTraitNodeEntry(currencySource->TraitNodeEntryID)) + continue; + + currencies[currencySource->TraitCurrencyID] += currencySource->Amount; + } + } + break; + default: + break; + } + } + } +} + +void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies) +{ + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, entry.TraitNodeID); + for (NodeGroup const* group : node->Groups) + for (TraitCostEntry const* cost : group->Costs) + cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank; + + auto nodeEntryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&entry](NodeEntry const& nodeEntry) { return int32(nodeEntry.Data->ID) == entry.TraitNodeEntryID; }); + if (nodeEntryItr != node->Entries.end()) + for (TraitCostEntry const* cost : nodeEntryItr->Costs) + cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank; + + for (TraitCostEntry const* cost : node->Costs) + cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank; + + if (Tree const* tree = Trinity::Containers::MapGetValuePtr(_traitTrees, node->Data->TraitTreeID)) + for (TraitCostEntry const* cost : tree->Costs) + cachedCurrencies[cost->TraitCurrencyID] += cost->Amount * entry.Rank; +} + +void FillSpentCurrenciesMap(WorldPackets::Traits::TraitConfig const& traitConfig, std::map<int32, int32>& cachedCurrencies) +{ + for (WorldPackets::Traits::TraitEntry const& entry : traitConfig.Entries) + FillSpentCurrenciesMap(entry, cachedCurrencies); +} + +bool MeetsTraitCondition(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, TraitCondEntry const* condition, + Optional<std::map<int32, int32>>& cachedCurrencies) +{ + if (condition->QuestID && !player->IsQuestRewarded(condition->QuestID)) + return false; + + if (condition->AchievementID && !player->HasAchieved(condition->AchievementID)) + return false; + + if (condition->SpecSetID && !sDB2Manager.IsSpecSetMember(condition->SpecSetID, player->GetPrimarySpecialization())) + return false; + + if (condition->TraitCurrencyID && condition->SpentAmountRequired) + { + if (!cachedCurrencies) + FillSpentCurrenciesMap(traitConfig, cachedCurrencies.emplace()); + + if (condition->TraitNodeGroupID) + { + auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first; + if (itr->second < condition->SpentAmountRequired) + return false; + } + else if (condition->TraitNodeID) + { + auto itr = cachedCurrencies->try_emplace(condition->TraitCurrencyID, 0).first; + if (itr->second < condition->SpentAmountRequired) + return false; + } + } + + if (condition->RequiredLevel && int32(player->GetLevel()) < condition->RequiredLevel) + return false; + + return true; +} + +std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player) +{ + std::vector<UF::TraitEntry> entries; + std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig); + if (!trees) + return entries; + + auto getOrCreateEntry = [&entries](int32 nodeId, int32 entryId) + { + auto itr = std::find_if(entries.begin(), entries.end(), [&](UF::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId; + }); + if (itr == entries.end()) + { + itr = entries.emplace(entries.end()); + itr->TraitNodeID = nodeId; + itr->TraitNodeEntryID = entryId; + itr->Rank = 0; + itr->GrantedRanks = 0; + } + return &*itr; + }; + + Optional<std::map<int32, int32>> cachedCurrencies; + + for (Tree const* tree : *trees) + { + for (Node const* node : tree->Nodes) + { + for (NodeEntry const& entry : node->Entries) + for (TraitCondEntry const* condition : entry.Conditions) + if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) + getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + + for (TraitCondEntry const* condition : node->Conditions) + if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) + for (NodeEntry const& entry : node->Entries) + getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + + for (NodeGroup const* group : node->Groups) + for (TraitCondEntry const* condition : group->Conditions) + if (condition->GetCondType() == TraitConditionType::Granted && MeetsTraitCondition(traitConfig, player, condition, cachedCurrencies)) + for (NodeEntry const& entry : node->Entries) + getOrCreateEntry(node->Data->ID, entry.Data->ID)->GrantedRanks += condition->GrantedRanks; + } + } + + return entries; +} + +bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry) +{ + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID); + if (!node) + return false; + + auto entryItr = std::find_if(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& entry) { return entry.Data->ID == uint32(traitEntry.TraitNodeEntryID); }); + if (entryItr == node->Entries.end()) + return false; + + if (entryItr->Data->MaxRanks < traitEntry.Rank + traitEntry.GrantedRanks) + return false; + + return true; +} + +TalentLearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, bool requireSpendingAllCurrencies /*= false*/) +{ + auto getNodeEntryCount = [&](int32 traitNodeId) + { + return std::count_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [traitNodeId](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == traitNodeId; + }); + }; + + auto getNodeEntry = [&](int32 traitNodeId, int32 traitNodeEntryId) + { + auto entryItr = std::find_if(traitConfig.Entries.begin(), traitConfig.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId; + }); + return entryItr != traitConfig.Entries.end() ? &*entryItr : nullptr; + }; + + auto isNodeFullyFilled = [&](Node const* node) + { + if (node->Data->GetType() == TraitNodeType::Selection) + return std::any_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry) + { + WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID); + return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks; + }); + + return std::all_of(node->Entries.begin(), node->Entries.end(), [&](NodeEntry const& nodeEntry) + { + WorldPackets::Traits::TraitEntry const* traitEntry = getNodeEntry(node->Data->ID, nodeEntry.Data->ID); + return traitEntry && (traitEntry->Rank + traitEntry->GrantedRanks) == nodeEntry.Data->MaxRanks; + }); + }; + + Optional<std::map<int32, int32>> spentCurrencies; + FillSpentCurrenciesMap(traitConfig, spentCurrencies.emplace()); + + for (WorldPackets::Traits::TraitEntry const& traitEntry : traitConfig.Entries) + { + if (!IsValidEntry(traitEntry)) + return TALENT_FAILED_UNKNOWN; + + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, traitEntry.TraitNodeID); + if (node->Data->GetType() == TraitNodeType::Selection) + if (getNodeEntryCount(traitEntry.TraitNodeID) != 1) + return TALENT_FAILED_UNKNOWN; + + for (NodeEntry const& entry : node->Entries) + for (TraitCondEntry const* condition : entry.Conditions) + if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible) + && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies)) + return TALENT_FAILED_UNKNOWN; + + for (TraitCondEntry const* condition : node->Conditions) + if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible) + && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies)) + return TALENT_FAILED_UNKNOWN; + + for (NodeGroup const* group : node->Groups) + for (TraitCondEntry const* condition : group->Conditions) + if ((condition->GetCondType() == TraitConditionType::Available || condition->GetCondType() == TraitConditionType::Visible) + && !MeetsTraitCondition(traitConfig, player, condition, spentCurrencies)) + return TALENT_FAILED_UNKNOWN; + + if (!node->ParentNodes.empty()) + { + bool hasAnyParentTrait = false; + for (auto const& [parentNode, edgeType] : node->ParentNodes) + { + if (!isNodeFullyFilled(parentNode)) + { + if (edgeType == TraitEdgeType::RequiredForAvailability) + return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE; + + continue; + } + + hasAnyParentTrait = true; + } + + if (!hasAnyParentTrait) + return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE; + } + } + + std::map<int32, int32> grantedCurrencies; + FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies); + + for (auto [traitCurrencyId, spentAmount] : *spentCurrencies) + { + if (sTraitCurrencyStore.AssertEntry(traitCurrencyId)->GetType() != TraitCurrencyType::TraitSourced) + continue; + + if (!spentAmount) + continue; + + int32* grantedCount = Trinity::Containers::MapGetValuePtr(grantedCurrencies, traitCurrencyId); + if (!grantedCount || *grantedCount < spentAmount) + return TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE; + + } + + if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType::Combat) + { + for (auto [traitCurrencyId, grantedAmount] : grantedCurrencies) + { + if (!grantedAmount) + continue; + + int32* spentAmount = Trinity::Containers::MapGetValuePtr(*spentCurrencies, traitCurrencyId); + if (!spentAmount || *spentAmount != grantedAmount) + return TALENT_FAILED_UNSPENT_TALENT_POINTS; + } + } + + return TALENT_LEARN_OK; +} + +std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId) +{ + return Trinity::Containers::MapGetValuePtr(_traitDefinitionEffectPointModifiers, traitDefinitionId); +} + +void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, Player const* player) +{ + traitConfig.Entries.clear(); + std::vector<Tree const*> const* trees = GetTreesForConfig(traitConfig); + if (!trees) + return; + + for (UF::TraitEntry const& grant : GetGrantedTraitEntriesForConfig(traitConfig, player)) + { + WorldPackets::Traits::TraitEntry& newEntry = traitConfig.Entries.emplace_back(); + newEntry.TraitNodeID = grant.TraitNodeID; + newEntry.TraitNodeEntryID = grant.TraitNodeEntryID; + newEntry.GrantedRanks = grant.GrantedRanks; + } + + std::map<int32, int32> currencies; + FillOwnedCurrenciesMap(traitConfig, player, currencies); + + if (std::vector<TraitTreeLoadoutEntryEntry const*> const* loadoutEntries = Trinity::Containers::MapGetValuePtr(_traitTreeLoadoutsByChrSpecialization, traitConfig.ChrSpecializationID)) + { + auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry* + { + auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry) + { + return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId; + }); + return entryItr != config.Entries.end() ? &*entryItr : nullptr; + }; + + for (TraitTreeLoadoutEntryEntry const* loadoutEntry : *loadoutEntries) + { + int32 addedRanks = loadoutEntry->NumPoints; + Node const* node = Trinity::Containers::MapGetValuePtr(_traitNodes, loadoutEntry->SelectedTraitNodeID); + + WorldPackets::Traits::TraitEntry newEntry; + newEntry.TraitNodeID = loadoutEntry->SelectedTraitNodeID; + newEntry.TraitNodeEntryID = loadoutEntry->SelectedTraitNodeEntryID; + if (!newEntry.TraitNodeEntryID) + newEntry.TraitNodeEntryID = node->Entries[0].Data->ID; + + WorldPackets::Traits::TraitEntry* entryInConfig = findEntry(traitConfig, newEntry.TraitNodeID, newEntry.TraitNodeEntryID); + + if (entryInConfig) + addedRanks -= entryInConfig->Rank; + + newEntry.Rank = addedRanks; + + if (!HasEnoughCurrency(newEntry, currencies)) + break; + + if (entryInConfig) + entryInConfig->Rank += addedRanks; + else + traitConfig.Entries.push_back(newEntry); + + TakeCurrencyCost(newEntry, currencies); + } + } +} +} diff --git a/src/server/game/Spells/TraitMgr.h b/src/server/game/Spells/TraitMgr.h new file mode 100644 index 00000000000..2350f2bb92d --- /dev/null +++ b/src/server/game/Spells/TraitMgr.h @@ -0,0 +1,57 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_TRAIT_MGR_H +#define TRINITY_TRAIT_MGR_H + +#include "Define.h" +#include <map> +#include <vector> + +class Player; +struct TraitDefinitionEffectPointsEntry; +enum class TraitConfigType : int32; +enum TalentLearnResult : int32; + +namespace UF +{ +struct TraitEntry; +} + +namespace WorldPackets::Traits +{ +struct TraitConfig; +struct TraitEntry; +} + +namespace TraitMgr +{ +constexpr uint32 COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID = 384255u; +constexpr uint32 MAX_COMBAT_TRAIT_CONFIGS = 10u; + +void Load(); +int32 GenerateNewTraitConfigId(); +TraitConfigType GetConfigTypeForTree(int32 traitTreeId); +void FillSpentCurrenciesMap(WorldPackets::Traits::TraitEntry const& entry, std::map<int32, int32>& cachedCurrencies); +std::vector<UF::TraitEntry> GetGrantedTraitEntriesForConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player); +bool IsValidEntry(WorldPackets::Traits::TraitEntry const& traitEntry); +TalentLearnResult ValidateConfig(WorldPackets::Traits::TraitConfig const& traitConfig, Player const* player, bool requireSpendingAllCurrencies = false); +std::vector<TraitDefinitionEffectPointsEntry const*> const* GetTraitDefinitionEffectPointModifiers(int32 traitDefinitionId); +void InitializeStarterBuildTraitConfig(WorldPackets::Traits::TraitConfig& traitConfig, Player const* player); +} + +#endif // TRINITY_TRAIT_MGR_H diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index d13d47f8564..2f4bb3ce75d 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -91,6 +91,7 @@ #include "SupportMgr.h" #include "TaxiPathGraph.h" #include "TerrainMgr.h" +#include "TraitMgr.h" #include "TransportMgr.h" #include "Unit.h" #include "UpdateTime.h" @@ -1872,6 +1873,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Spell Totem models..."); sSpellMgr->LoadSpellTotemModel(); + TC_LOG_INFO("server.loading", "Loading Traits..."); + TraitMgr::Load(); + TC_LOG_INFO("server.loading", "Loading languages..."); // must be after LoadSpellInfoStore and LoadSkillLineAbilityMap sLanguageMgr->LoadLanguages(); |