From 49a07ebcceddec26b887bbcbc6545b05bac811dd Mon Sep 17 00:00:00 2001 From: hondacrx Date: Wed, 21 Jun 2023 09:19:40 -0400 Subject: [PATCH] Core/Items: Item bonus generation improvements * Pass ItemContext to item creation wherever possible * Support scaling item levels with m+ keystone levels (not used currently) * Fixed item link validation when client sends it as default uninitialized bonus list with context only * Support scaling items depending on current active season (seasons not implemented) * Implemented content tuning redirection Port From (https://github.com/TrinityCore/TrinityCore/commit/0fb8765a6638fd947b59fce44d5c31251d0cdadd) --- Source/Framework/Constants/ItemConst.cs | 10 +- .../Database/Databases/HotfixDatabase.cs | 49 +- Source/Game/Achievements/CriteriaHandler.cs | 2 +- Source/Game/Chat/Commands/MiscCommands.cs | 20 +- Source/Game/DataStorage/CliDB.cs | 16 + Source/Game/DataStorage/DB2Manager.cs | 263 ++------- Source/Game/DataStorage/Structs/C_Records.cs | 28 + Source/Game/DataStorage/Structs/I_Records.cs | 37 +- Source/Game/DataStorage/Structs/M_Records.cs | 8 + Source/Game/DataStorage/Structs/P_Records.cs | 8 + Source/Game/Entities/Creature/Creature.cs | 6 +- Source/Game/Entities/GameObject/GameObject.cs | 22 +- Source/Game/Entities/Item/Item.cs | 70 ++- Source/Game/Entities/Item/ItemBonusManager.cs | 506 ++++++++++++++++++ Source/Game/Entities/Item/ItemEnchantment.cs | 2 +- Source/Game/Entities/Player/Player.Combat.cs | 2 +- Source/Game/Entities/Player/Player.Items.cs | 15 +- Source/Game/Entities/Player/Player.Quest.cs | 16 +- Source/Game/Entities/TemporarySummon.cs | 2 +- Source/Game/Entities/Unit/Unit.Combat.cs | 6 +- Source/Game/Entities/Unit/Unit.cs | 2 +- Source/Game/Globals/ObjectManager.cs | 6 +- Source/Game/Handlers/MailHandler.cs | 4 +- Source/Game/Handlers/QuestHandler.cs | 2 +- Source/Game/Loot/Loot.cs | 6 +- Source/Game/Loot/LootManager.cs | 4 +- Source/Game/Maps/Map.cs | 13 - Source/Game/Spells/SpellInfo.cs | 2 +- Source/Game/World/WorldManager.cs | 3 + Source/Scripts/Spells/Monk.cs | 2 +- 30 files changed, 796 insertions(+), 336 deletions(-) create mode 100644 Source/Game/Entities/Item/ItemBonusManager.cs diff --git a/Source/Framework/Constants/ItemConst.cs b/Source/Framework/Constants/ItemConst.cs index 9ddd20e33..e0e625934 100644 --- a/Source/Framework/Constants/ItemConst.cs +++ b/Source/Framework/Constants/ItemConst.cs @@ -1132,7 +1132,15 @@ namespace Framework.Constants SquishUsingItemLevelAsPlayerLevel = 0x4000, AlwaysShowPriceInTooltip = 0x8000, CosmeticItem = 0x10000, - NoSpellEffectTooltipPrefixes = 0x20000 + NoSpellEffectTooltipPrefixes = 0x20000, + IgnoreCosmeticCollectionBehavior = 0x40000, + NpcOnly = 0x80000, + NotRestorable = 0x100000, + DontDisplayAsCraftingReagent = 0x200000, + DisplayReagentQualityAsCraftedQuality = 0x400000, + NoSalvage = 0x800000, + Recraftable = 0x1000000, + CcTrinket = 0x2000000, } [Flags] diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index 0d9b57af3..d357655a2 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -212,6 +212,10 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_CFG_REGIONS, "SELECT ID, Tag, RegionID, Raidorigin, RegionGroupMask, ChallengeOrigin FROM cfg_regions" + " WHERE (`VerifiedBuild` > 0) = ?"); + // ChallengeModeItemBonusOverride.db2 + PrepareStatement(HotfixStatements.SEL_CHALLENGE_MODE_ITEM_BONUS_OVERRIDE, "SELECT ID, ItemBonusTreeGroupID, DstItemBonusTreeID, Type, Value, " + + "MythicPlusSeasonID, PvPSeasonID, SrcItemBonusTreeID FROM challenge_mode_item_bonus_override WHERE (`VerifiedBuild` > 0) = ?"); + // CharTitles.db2 PrepareStatement(HotfixStatements.SEL_CHAR_TITLES, "SELECT ID, Name, Name1, MaskID, Flags FROM char_titles WHERE (`VerifiedBuild` > 0) = ?"); PrepareStatement(HotfixStatements.SEL_CHAR_TITLES_LOCALE, "SELECT ID, Name_lang, Name1_lang FROM char_titles_locale WHERE (`VerifiedBuild` > 0) = ?" + @@ -327,6 +331,10 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_CINEMATIC_SEQUENCES, "SELECT ID, SoundID, Camera1, Camera2, Camera3, Camera4, Camera5, Camera6, Camera7, Camera8" + " FROM cinematic_sequences WHERE (`VerifiedBuild` > 0) = ?"); + // ConditionalContentTuning.db2 + PrepareStatement(HotfixStatements.SEL_CONDITIONAL_CONTENT_TUNING, "SELECT ID, OrderIndex, RedirectContentTuningID, RedirectFlag, ParentContentTuningID" + + " FROM conditional_content_tuning WHERE (`VerifiedBuild` > 0) = ?"); + // ContentTuning.db2 PrepareStatement(HotfixStatements.SEL_CONTENT_TUNING, "SELECT ID, Flags, ExpansionID, MinLevel, MaxLevel, MinLevelType, MaxLevelType, TargetLevelDelta, " + "TargetLevelMaxDelta, TargetLevelMin, TargetLevelMax, MinItemLevel, QuestXpMultiplier FROM content_tuning WHERE (`VerifiedBuild` > 0) = ?"); @@ -335,6 +343,10 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_CONTENT_TUNING_X_EXPECTED, "SELECT ID, ExpectedStatModID, MinMythicPlusSeasonID, MaxMythicPlusSeasonID, " + "ContentTuningID FROM content_tuning_x_expected WHERE (`VerifiedBuild` > 0) = ?"); + // ContentTuningXLabel.db2 + PrepareStatement(HotfixStatements.SEL_CONTENT_TUNING_X_LABEL, "SELECT ID, LabelID, ContentTuningID FROM content_tuning_x_label" + + " WHERE (`VerifiedBuild` > 0) = ?"); + // ConversationLine.db2 PrepareStatement(HotfixStatements.SEL_CONVERSATION_LINE, "SELECT ID, BroadcastTextID, SpellVisualKitID, AdditionalDuration, NextConversationLineID, " + "AnimKitID, SpeechType, StartAnimation, EndAnimation FROM conversation_line WHERE (`VerifiedBuild` > 0) = ?"); @@ -653,13 +665,20 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_ITEM_BONUS, "SELECT ID, Value1, Value2, Value3, Value4, ParentItemBonusListID, Type, OrderIndex FROM item_bonus" + " WHERE (`VerifiedBuild` > 0) = ?"); + // ItemBonusListGroupEntry.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_LIST_GROUP_ENTRY, "SELECT ID, ItemBonusListGroupID, ItemBonusListID, ItemLevelSelectorID, SequenceValue, " + + "ItemExtendedCostID, PlayerConditionID, Flags, ItemLogicalCostGroupID FROM item_bonus_list_group_entry WHERE (`VerifiedBuild` > 0) = ?"); + // ItemBonusListLevelDelta.db2 PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_LIST_LEVEL_DELTA, "SELECT ItemLevelDelta, ID FROM item_bonus_list_level_delta" + " WHERE (`VerifiedBuild` > 0) = ?"); + // ItemBonusTree.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_TREE, "SELECT ID, Flags, InventoryTypeSlotMask FROM item_bonus_tree WHERE (`VerifiedBuild` > 0) = ?"); + // ItemBonusTreeNode.db2 PrepareStatement(HotfixStatements.SEL_ITEM_BONUS_TREE_NODE, "SELECT ID, ItemContext, ChildItemBonusTreeID, ChildItemBonusListID, ChildItemLevelSelectorID, " + - "ChildItemBonusListGroupID, IblGroupPointsModSetID, Unknown1010_1, Unknown1010_2, ParentItemBonusTreeID FROM item_bonus_tree_node" + + "ChildItemBonusListGroupID, IblGroupPointsModSetID, MinMythicPlusLevel, MaxMythicPlusLevel, ParentItemBonusTreeID FROM item_bonus_tree_node" + " WHERE (`VerifiedBuild` > 0) = ?"); // ItemChildEquipment.db2 @@ -670,6 +689,10 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_ITEM_CLASS, "SELECT ID, ClassName, ClassID, PriceModifier, Flags FROM item_class WHERE (`VerifiedBuild` > 0) = ?"); PrepareStatement(HotfixStatements.SEL_ITEM_CLASS_LOCALE, "SELECT ID, ClassName_lang FROM item_class_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?"); + // ItemContextPickerEntry.db2 + PrepareStatement(HotfixStatements.SEL_ITEM_CONTEXT_PICKER_ENTRY, "SELECT ID, ItemCreationContext, OrderIndex, PVal, LabelID, Flags, PlayerConditionID, " + + "ItemContextPickerID FROM item_context_picker_entry WHERE (`VerifiedBuild` > 0) = ?"); + // ItemCurrencyCost.db2 PrepareStatement(HotfixStatements.SEL_ITEM_CURRENCY_COST, "SELECT ID, ItemID FROM item_currency_cost WHERE (`VerifiedBuild` > 0) = ?"); @@ -918,6 +941,10 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_MOVIE, "SELECT ID, Volume, KeyID, AudioFileDataID, SubtitleFileDataID, SubtitleFileFormat FROM movie" + " WHERE (`VerifiedBuild` > 0) = ?"); + // MythicPlusSeason.db2 + PrepareStatement(HotfixStatements.SEL_MYTHIC_PLUS_SEASON, "SELECT ID, MilestoneSeason, ExpansionLevel, HeroicLFGDungeonMinGear FROM mythic_plus_season" + + " WHERE (`VerifiedBuild` > 0) = ?"); + // NameGen.db2 PrepareStatement(HotfixStatements.SEL_NAME_GEN, "SELECT ID, Name, RaceID, Sex FROM name_gen WHERE (`VerifiedBuild` > 0) = ?"); @@ -991,6 +1018,10 @@ namespace Framework.Database // PvpItem.db2 PrepareStatement(HotfixStatements.SEL_PVP_ITEM, "SELECT ID, ItemID, ItemLevelDelta FROM pvp_item WHERE (`VerifiedBuild` > 0) = ?"); + // PvpSeason.db2 + PrepareStatement(HotfixStatements.SEL_PVP_SEASON, "SELECT ID, MilestoneSeason, AllianceAchievementID, HordeAchievementID FROM pvp_season" + + " WHERE (`VerifiedBuild` > 0) = ?"); + // PvpTalent.db2 PrepareStatement(HotfixStatements.SEL_PVP_TALENT, "SELECT Description, ID, SpecID, SpellID, OverridesSpellID, Flags, ActionBarSpellID, PvpTalentCategoryID, " + "LevelRequired, PlayerConditionID FROM pvp_talent WHERE (`VerifiedBuild` > 0) = ?"); @@ -1646,6 +1677,8 @@ namespace Framework.Database SEL_CFG_REGIONS, + SEL_CHALLENGE_MODE_ITEM_BONUS_OVERRIDE, + SEL_CHAR_TITLES, SEL_CHAR_TITLES_LOCALE, @@ -1692,10 +1725,14 @@ namespace Framework.Database SEL_CINEMATIC_SEQUENCES, + SEL_CONDITIONAL_CONTENT_TUNING, + SEL_CONTENT_TUNING, SEL_CONTENT_TUNING_X_EXPECTED, + SEL_CONTENT_TUNING_X_LABEL, + SEL_CONVERSATION_LINE, SEL_CORRUPTION_EFFECTS, @@ -1847,8 +1884,12 @@ namespace Framework.Database SEL_ITEM_BONUS, + SEL_ITEM_BONUS_LIST_GROUP_ENTRY, + SEL_ITEM_BONUS_LIST_LEVEL_DELTA, + SEL_ITEM_BONUS_TREE, + SEL_ITEM_BONUS_TREE_NODE, SEL_ITEM_CHILD_EQUIPMENT, @@ -1856,6 +1897,8 @@ namespace Framework.Database SEL_ITEM_CLASS, SEL_ITEM_CLASS_LOCALE, + SEL_ITEM_CONTEXT_PICKER_ENTRY, + SEL_ITEM_CURRENCY_COST, SEL_ITEM_DAMAGE_AMMO, @@ -1974,6 +2017,8 @@ namespace Framework.Database SEL_MOVIE, + SEL_MYTHIC_PLUS_SEASON, + SEL_NAME_GEN, SEL_NAMES_PROFANITY, @@ -2006,6 +2051,8 @@ namespace Framework.Database SEL_PVP_ITEM, + SEL_PVP_SEASON, + SEL_PVP_TALENT, SEL_PVP_TALENT_LOCALE, diff --git a/Source/Game/Achievements/CriteriaHandler.cs b/Source/Game/Achievements/CriteriaHandler.cs index 373100d7b..128c1c706 100644 --- a/Source/Game/Achievements/CriteriaHandler.cs +++ b/Source/Game/Achievements/CriteriaHandler.cs @@ -2736,7 +2736,7 @@ namespace Game.Achievements } case ModifierTreeType.PlayerHasItemWithBonusListFromTreeAndQuality: // 222 { - var bonusListIDs = Global.DB2Mgr.GetAllItemBonusTreeBonuses(reqValue); + var bonusListIDs = ItemBonusMgr.GetAllBonusListsForTree(reqValue); if (bonusListIDs.Empty()) return false; diff --git a/Source/Game/Chat/Commands/MiscCommands.cs b/Source/Game/Chat/Commands/MiscCommands.cs index 8e20c1fe1..e2e08f309 100644 --- a/Source/Game/Chat/Commands/MiscCommands.cs +++ b/Source/Game/Chat/Commands/MiscCommands.cs @@ -2096,9 +2096,9 @@ namespace Game.Chat if (!context.IsEmpty()) { itemContext = context.ToEnum(); - if (itemContext != ItemContext.None && itemContext < ItemContext.Max) + if (itemContext < ItemContext.Max) { - var contextBonuses = Global.DB2Mgr.GetDefaultItemBonusTree(itemId, itemContext); + var contextBonuses = ItemBonusMgr.GetBonusListsForItem(itemId, new(itemContext)); bonusListIDs.AddRange(contextBonuses); } } @@ -2156,7 +2156,7 @@ namespace Game.Chat return false; } - Item item = playerTarget.StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, itemContext, bonusListIDs); + Item item = playerTarget.StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, itemContext, bonusListIDs.Empty() ? null : bonusListIDs); // remove binding (let GM give it to another player later) if (player == playerTarget) @@ -2208,7 +2208,7 @@ namespace Game.Chat ItemContext itemContext = ItemContext.None; if (context.HasValue) - itemContext = (ItemContext)context; + itemContext = (ItemContext)context.Value; Player player = handler.GetSession().GetPlayer(); Player playerTarget = handler.GetSelectedPlayer(); @@ -2230,13 +2230,13 @@ namespace Game.Chat if (msg == InventoryResult.Ok) { List bonusListIDsForItem = new(bonusListIDs); // copy, bonuses for each depending on context might be different for each item - if (itemContext != ItemContext.None && itemContext < ItemContext.Max) + if (itemContext < ItemContext.Max) { - var contextBonuses = Global.DB2Mgr.GetDefaultItemBonusTree(template.Value.GetId(), itemContext); + var contextBonuses = ItemBonusMgr.GetBonusListsForItem(template.Value.GetId(), new(itemContext)); bonusListIDsForItem.AddRange(contextBonuses); } - Item item = playerTarget.StoreNewItem(dest, template.Value.GetId(), true, 0, null, itemContext, bonusListIDsForItem); + Item item = playerTarget.StoreNewItem(dest, template.Value.GetId(), true, 0, null, itemContext, bonusListIDsForItem.Empty() ? null : bonusListIDsForItem); // remove binding (let GM give it to another player later) if (player == playerTarget) @@ -2330,9 +2330,9 @@ namespace Game.Chat if (!context.IsEmpty()) { itemContext = context.ToEnum(); - if (itemContext != ItemContext.None && itemContext < ItemContext.Max) + if (itemContext < ItemContext.Max) { - var contextBonuses = Global.DB2Mgr.GetDefaultItemBonusTree(itemId, itemContext); + var contextBonuses = ItemBonusMgr.GetBonusListsForItem(itemId, new(itemContext)); bonusListIDs.AddRange(contextBonuses); } } @@ -2396,7 +2396,7 @@ namespace Game.Chat return false; } - Item item = playerTarget.StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, itemContext, bonusListIDs); + Item item = playerTarget.StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, itemContext, bonusListIDs.Empty() ? null : bonusListIDs); // remove binding (let GM give it to another player later) if (player == playerTarget) diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index 05e9fecff..155da11fc 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -87,6 +87,7 @@ namespace Game.DataStorage BroadcastTextStorage = ReadDB2("BroadcastText.db2", HotfixStatements.SEL_BROADCAST_TEXT, HotfixStatements.SEL_BROADCAST_TEXT_LOCALE); BroadcastTextDurationStorage = ReadDB2("BroadcastTextDuration.db2", HotfixStatements.SEL_BROADCAST_TEXT_DURATION); CfgRegionsStorage = ReadDB2("Cfg_Regions.db2", HotfixStatements.SEL_CFG_REGIONS); + ChallengeModeItemBonusOverrideStorage = ReadDB2("ChallengeModeItemBonusOverride.db2", HotfixStatements.SEL_CHALLENGE_MODE_ITEM_BONUS_OVERRIDE); CharTitlesStorage = ReadDB2("CharTitles.db2", HotfixStatements.SEL_CHAR_TITLES, HotfixStatements.SEL_CHAR_TITLES_LOCALE); CharacterLoadoutStorage = ReadDB2("CharacterLoadout.db2", HotfixStatements.SEL_CHARACTER_LOADOUT); CharacterLoadoutItemStorage = ReadDB2("CharacterLoadoutItem.db2", HotfixStatements.SEL_CHARACTER_LOADOUT_ITEM); @@ -106,8 +107,10 @@ namespace Game.DataStorage ChrSpecializationStorage = ReadDB2("ChrSpecialization.db2", HotfixStatements.SEL_CHR_SPECIALIZATION, HotfixStatements.SEL_CHR_SPECIALIZATION_LOCALE); CinematicCameraStorage = ReadDB2("CinematicCamera.db2", HotfixStatements.SEL_CINEMATIC_CAMERA); CinematicSequencesStorage = ReadDB2("CinematicSequences.db2", HotfixStatements.SEL_CINEMATIC_SEQUENCES); + ConditionalContentTuningStorage = ReadDB2("ConditionalContentTuning.db2", HotfixStatements.SEL_CONDITIONAL_CONTENT_TUNING); ContentTuningStorage = ReadDB2("ContentTuning.db2", HotfixStatements.SEL_CONTENT_TUNING); ContentTuningXExpectedStorage = ReadDB2("ContentTuningXExpected.db2", HotfixStatements.SEL_CONTENT_TUNING_X_EXPECTED); + ContentTuningXLabelStorage = ReadDB2("ContentTuningXLabel.db2", HotfixStatements.SEL_CONTENT_TUNING_X_LABEL); ConversationLineStorage = ReadDB2("ConversationLine.db2", HotfixStatements.SEL_CONVERSATION_LINE); CorruptionEffectsStorage = ReadDB2("CorruptionEffects.db2", HotfixStatements.SEL_CORRUPTION_EFFECTS); CreatureDisplayInfoStorage = ReadDB2("CreatureDisplayInfo.db2", HotfixStatements.SEL_CREATURE_DISPLAY_INFO); @@ -173,10 +176,13 @@ namespace Game.DataStorage ItemArmorTotalStorage = ReadDB2("ItemArmorTotal.db2", HotfixStatements.SEL_ITEM_ARMOR_TOTAL); //ItemBagFamilyStorage = ReadDB2("ItemBagFamily.db2", HotfixStatements.SEL_ITEM_BAG_FAMILY, HotfixStatements.SEL_ITEM_BAG_FAMILY_LOCALE); ItemBonusStorage = ReadDB2("ItemBonus.db2", HotfixStatements.SEL_ITEM_BONUS); + ItemBonusListGroupEntryStorage = ReadDB2("ItemBonusListGroupEntry.db2", HotfixStatements.SEL_ITEM_BONUS_LIST_GROUP_ENTRY); ItemBonusListLevelDeltaStorage = ReadDB2("ItemBonusListLevelDelta.db2", HotfixStatements.SEL_ITEM_BONUS_LIST_LEVEL_DELTA); + ItemBonusTreeStorage = ReadDB2("ItemBonusTree.db2", HotfixStatements.SEL_ITEM_BONUS_TREE); ItemBonusTreeNodeStorage = ReadDB2("ItemBonusTreeNode.db2", HotfixStatements.SEL_ITEM_BONUS_TREE_NODE); ItemChildEquipmentStorage = ReadDB2("ItemChildEquipment.db2", HotfixStatements.SEL_ITEM_CHILD_EQUIPMENT); ItemClassStorage = ReadDB2("ItemClass.db2", HotfixStatements.SEL_ITEM_CLASS, HotfixStatements.SEL_ITEM_CLASS_LOCALE); + ItemContextPickerEntryStorage = ReadDB2("ItemContextPickerEntry.db2", HotfixStatements.SEL_ITEM_CONTEXT_PICKER_ENTRY); ItemCurrencyCostStorage = ReadDB2("ItemCurrencyCost.db2", HotfixStatements.SEL_ITEM_CURRENCY_COST); ItemDamageAmmoStorage = ReadDB2("ItemDamageAmmo.db2", HotfixStatements.SEL_ITEM_DAMAGE_AMMO); ItemDamageOneHandStorage = ReadDB2("ItemDamageOneHand.db2", HotfixStatements.SEL_ITEM_DAMAGE_ONE_HAND); @@ -228,6 +234,7 @@ namespace Game.DataStorage MountTypeXCapabilityStorage = ReadDB2("MountTypeXCapability.db2", HotfixStatements.SEL_MOUNT_TYPE_X_CAPABILITY); MountXDisplayStorage = ReadDB2("MountXDisplay.db2", HotfixStatements.SEL_MOUNT_X_DISPLAY); MovieStorage = ReadDB2("Movie.db2", HotfixStatements.SEL_MOVIE); + MythicPlusSeasonStorage = ReadDB2("MythicPlusSeason.db2", HotfixStatements.SEL_MYTHIC_PLUS_SEASON); NameGenStorage = ReadDB2("NameGen.db2", HotfixStatements.SEL_NAME_GEN); NamesProfanityStorage = ReadDB2("NamesProfanity.db2", HotfixStatements.SEL_NAMES_PROFANITY); NamesReservedStorage = ReadDB2("NamesReserved.db2", HotfixStatements.SEL_NAMES_RESERVED, HotfixStatements.SEL_NAMES_RESERVED_LOCALE); @@ -243,6 +250,7 @@ namespace Game.DataStorage PrestigeLevelInfoStorage = ReadDB2("PrestigeLevelInfo.db2", HotfixStatements.SEL_PRESTIGE_LEVEL_INFO, HotfixStatements.SEL_PRESTIGE_LEVEL_INFO_LOCALE); PvpDifficultyStorage = ReadDB2("PVPDifficulty.db2", HotfixStatements.SEL_PVP_DIFFICULTY); PvpItemStorage = ReadDB2("PVPItem.db2", HotfixStatements.SEL_PVP_ITEM); + PvpSeasonStorage = ReadDB2("PvpSeason.db2", HotfixStatements.SEL_PVP_SEASON); PvpTalentStorage = ReadDB2("PvpTalent.db2", HotfixStatements.SEL_PVP_TALENT, HotfixStatements.SEL_PVP_TALENT_LOCALE); PvpTalentCategoryStorage = ReadDB2("PvpTalentCategory.db2", HotfixStatements.SEL_PVP_TALENT_CATEGORY); PvpTalentSlotUnlockStorage = ReadDB2("PvpTalentSlotUnlock.db2", HotfixStatements.SEL_PVP_TALENT_SLOT_UNLOCK); @@ -511,6 +519,7 @@ namespace Game.DataStorage public static DB6Storage BroadcastTextStorage; public static DB6Storage BroadcastTextDurationStorage; public static DB6Storage CfgRegionsStorage; + public static DB6Storage ChallengeModeItemBonusOverrideStorage; public static DB6Storage CharTitlesStorage; public static DB6Storage CharacterLoadoutStorage; public static DB6Storage CharacterLoadoutItemStorage; @@ -530,8 +539,10 @@ namespace Game.DataStorage public static DB6Storage ChrSpecializationStorage; public static DB6Storage CinematicCameraStorage; public static DB6Storage CinematicSequencesStorage; + public static DB6Storage ConditionalContentTuningStorage; public static DB6Storage ContentTuningStorage; public static DB6Storage ContentTuningXExpectedStorage; + public static DB6Storage ContentTuningXLabelStorage; public static DB6Storage ConversationLineStorage; public static DB6Storage CorruptionEffectsStorage; public static DB6Storage CreatureDisplayInfoStorage; @@ -597,8 +608,11 @@ namespace Game.DataStorage public static DB6Storage ItemArmorTotalStorage; //public static DB6Storage ItemBagFamilyStorage; public static DB6Storage ItemBonusStorage; + public static DB6Storage ItemBonusListGroupEntryStorage; public static DB6Storage ItemBonusListLevelDeltaStorage; + public static DB6Storage ItemBonusTreeStorage; public static DB6Storage ItemBonusTreeNodeStorage; + public static DB6Storage ItemContextPickerEntryStorage; public static DB6Storage ItemClassStorage; public static DB6Storage ItemChildEquipmentStorage; public static DB6Storage ItemCurrencyCostStorage; @@ -652,6 +666,7 @@ namespace Game.DataStorage public static DB6Storage MountTypeXCapabilityStorage; public static DB6Storage MountXDisplayStorage; public static DB6Storage MovieStorage; + public static DB6Storage MythicPlusSeasonStorage; public static DB6Storage NameGenStorage; public static DB6Storage NamesProfanityStorage; public static DB6Storage NamesReservedStorage; @@ -667,6 +682,7 @@ namespace Game.DataStorage public static DB6Storage PrestigeLevelInfoStorage; public static DB6Storage PvpDifficultyStorage; public static DB6Storage PvpItemStorage; + public static DB6Storage PvpSeasonStorage; public static DB6Storage PvpTalentStorage; public static DB6Storage PvpTalentCategoryStorage; public static DB6Storage PvpTalentSlotUnlockStorage; diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 905eeb57b..195a1ee3d 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -90,10 +90,6 @@ namespace Game.DataStorage _azeriteTierUnlockLevels[key][azeriteTierUnlock.Tier] = azeriteTierUnlock.AzeriteLevel; } - MultiMap azeriteUnlockMappings = new(); - foreach (AzeriteUnlockMappingRecord azeriteUnlockMapping in AzeriteUnlockMappingStorage.Values) - azeriteUnlockMappings.Add(azeriteUnlockMapping.AzeriteUnlockMappingSetID, azeriteUnlockMapping); - foreach (BattlemasterListRecord battlemaster in BattlemasterListStorage.Values) { if (battlemaster.MaxLevel < battlemaster.MinLevel) @@ -230,12 +226,18 @@ namespace Game.DataStorage _chrSpecializationsByIndex[storageIndex][chrSpec.OrderIndex] = chrSpec; } + foreach (ConditionalContentTuningRecord conditionalContentTuning in ConditionalContentTuningStorage.Values) + _conditionalContentTuning.Add(conditionalContentTuning.ParentContentTuningID, conditionalContentTuning); + foreach (ContentTuningXExpectedRecord contentTuningXExpectedStat in ContentTuningXExpectedStorage.Values) { if (ExpectedStatModStorage.ContainsKey(contentTuningXExpectedStat.ExpectedStatModID)) _expectedStatModsByContentTuning.Add(contentTuningXExpectedStat.ContentTuningID, contentTuningXExpectedStat); } + foreach (ContentTuningXLabelRecord contentTuningXLabel in ContentTuningXLabelStorage.Values) + _contentTuningLabels.Add((contentTuningXLabel.ContentTuningID, contentTuningXLabel.LabelID)); + foreach (CurrencyContainerRecord currencyContainer in CurrencyContainerStorage.Values) _currencyContainers.Add(currencyContainer.CurrencyTypesID, currencyContainer); @@ -283,15 +285,6 @@ namespace Game.DataStorage foreach (GlyphRequiredSpecRecord glyphRequiredSpec in GlyphRequiredSpecStorage.Values) _glyphRequiredSpecs.Add(glyphRequiredSpec.GlyphPropertiesID, glyphRequiredSpec.ChrSpecializationID); - foreach (var bonus in ItemBonusStorage.Values) - _itemBonusLists.Add(bonus.ParentItemBonusListID, bonus); - - foreach (ItemBonusListLevelDeltaRecord itemBonusListLevelDelta in ItemBonusListLevelDeltaStorage.Values) - _itemLevelDeltaToBonusListContainer[itemBonusListLevelDelta.ItemLevelDelta] = itemBonusListLevelDelta.Id; - - foreach (var bonusTreeNode in ItemBonusTreeNodeStorage.Values) - _itemBonusTrees.Add(bonusTreeNode.ParentItemBonusTreeID, bonusTreeNode); - foreach (ItemChildEquipmentRecord itemChildEquipment in ItemChildEquipmentStorage.Values) { //ASSERT(_itemChildEquipment.find(itemChildEquipment.ParentItemID) == _itemChildEquipment.end(), "Item must have max 1 child item."); @@ -311,9 +304,6 @@ namespace Game.DataStorage foreach (ItemLimitCategoryConditionRecord condition in ItemLimitCategoryConditionStorage.Values) _itemCategoryConditions.Add(condition.ParentItemLimitCategoryID, condition); - foreach (ItemLevelSelectorQualityRecord itemLevelSelectorQuality in ItemLevelSelectorQualityStorage.Values) - _itemLevelQualitySelectorQualities.Add(itemLevelSelectorQuality.ParentILSQualitySetID, itemLevelSelectorQuality); - foreach (var appearanceMod in ItemModifiedAppearanceStorage.Values) { //ASSERT(appearanceMod.ItemID <= 0xFFFFFF); @@ -326,12 +316,6 @@ namespace Game.DataStorage foreach (var itemSpecOverride in ItemSpecOverrideStorage.Values) _itemSpecOverrides.Add(itemSpecOverride.ItemID, itemSpecOverride); - foreach (var itemBonusTreeAssignment in ItemXBonusTreeStorage.Values) - _itemToBonusTree.Add(itemBonusTreeAssignment.ItemID, itemBonusTreeAssignment.ItemBonusTreeID); - - foreach (var pair in _azeriteEmpoweredItems) - LoadAzeriteEmpoweredItemUnlockMappings(azeriteUnlockMappings, pair.Key); - foreach (JournalTierRecord journalTier in JournalTierStorage.Values) _journalTiersByIndex.Add(journalTier); @@ -1038,9 +1022,18 @@ namespace Game.DataStorage return GetChrSpecializationByIndex(class_, PlayerConst.InitialSpecializationIndex); } - public ContentTuningLevels? GetContentTuningData(uint contentTuningId, uint replacementConditionMask, bool forItem = false) + public uint GetRedirectedContentTuningId(uint contentTuningId, uint redirectFlag) { - ContentTuningRecord contentTuning = ContentTuningStorage.LookupByKey(contentTuningId); + foreach (var conditionalContentTuning in _conditionalContentTuning.LookupByKey(contentTuningId)) + if ((conditionalContentTuning.RedirectFlag & redirectFlag) != 0) + return (uint)conditionalContentTuning.RedirectContentTuningID; + + return contentTuningId; + } + + public ContentTuningLevels? GetContentTuningData(uint contentTuningId, uint redirectFlag, bool forItem = false) + { + ContentTuningRecord contentTuning = ContentTuningStorage.LookupByKey(GetRedirectedContentTuningId(contentTuningId, redirectFlag)); if (contentTuning == null) return null; @@ -1077,6 +1070,11 @@ namespace Game.DataStorage return levels; } + public bool HasContentTuningLabel(uint contentTuningId, int label) + { + return _contentTuningLabels.Contains((contentTuningId, label)); + } + public string GetCreatureFamilyPetName(CreatureFamily petfamily, Locale locale) { if (petfamily == CreatureFamily.None) @@ -1261,20 +1259,26 @@ namespace Game.DataStorage return null; } - float ExpectedStatModReducer(float mod, ContentTuningXExpectedRecord contentTuningXExpected, ExpectedStatType stat) + float ExpectedStatModReducer(float mod, ContentTuningXExpectedRecord contentTuningXExpected, ExpectedStatType stat, int ActiveMilestoneSeason) { if (contentTuningXExpected == null) return mod; - //if (contentTuningXExpected->MinMythicPlusSeasonID) - // if (MythicPlusSeasonEntry const* mythicPlusSeason = sMythicPlusSeasonStore.LookupEntry(contentTuningXExpected->MinMythicPlusSeasonID)) - // if (MythicPlusSubSeason < mythicPlusSeason->SubSeason) - // return mod; + if (contentTuningXExpected.MinMythicPlusSeasonID != 0) + { + var mythicPlusSeason = MythicPlusSeasonStorage.LookupByKey(contentTuningXExpected.MinMythicPlusSeasonID); + if (mythicPlusSeason != null) + if (ActiveMilestoneSeason < mythicPlusSeason.MilestoneSeason) + return mod; + } - //if (contentTuningXExpected->MaxMythicPlusSeasonID) - // if (MythicPlusSeasonEntry const* mythicPlusSeason = sMythicPlusSeasonStore.LookupEntry(contentTuningXExpected->MaxMythicPlusSeasonID)) - // if (MythicPlusSubSeason >= mythicPlusSeason->SubSeason) - // return mod; + if (contentTuningXExpected.MaxMythicPlusSeasonID != 0) + { + var mythicPlusSeason = MythicPlusSeasonStorage.LookupByKey(contentTuningXExpected.MaxMythicPlusSeasonID); + if (mythicPlusSeason != null) + if (ActiveMilestoneSeason >= mythicPlusSeason.MilestoneSeason) + return mod; + } var expectedStatMod = ExpectedStatModStorage.LookupByKey(contentTuningXExpected.ExpectedStatModID); switch (stat) @@ -1300,11 +1304,9 @@ namespace Game.DataStorage } return mod; - - // int32 MythicPlusSubSeason = 0; } - public float EvaluateExpectedStat(ExpectedStatType stat, uint level, int expansion, uint contentTuningId, Class unitClass) + public float EvaluateExpectedStat(ExpectedStatType stat, uint level, int expansion, uint contentTuningId, Class unitClass, int mythicPlusMilestoneSeason) { var expectedStatRecord = _expectedStatsByLevel.LookupByKey(Tuple.Create(level, expansion)); if (expectedStatRecord == null) @@ -1338,56 +1340,56 @@ namespace Game.DataStorage case ExpectedStatType.CreatureHealth: value = expectedStatRecord.CreatureHealth; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.CreatureHealthMod; break; case ExpectedStatType.PlayerHealth: value = expectedStatRecord.PlayerHealth; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.PlayerHealthMod; break; case ExpectedStatType.CreatureAutoAttackDps: value = expectedStatRecord.CreatureAutoAttackDps; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.CreatureAutoAttackDPSMod; break; case ExpectedStatType.CreatureArmor: value = expectedStatRecord.CreatureArmor; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.CreatureArmorMod; break; case ExpectedStatType.PlayerMana: value = expectedStatRecord.PlayerMana; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.PlayerManaMod; break; case ExpectedStatType.PlayerPrimaryStat: value = expectedStatRecord.PlayerPrimaryStat; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.PlayerPrimaryStatMod; break; case ExpectedStatType.PlayerSecondaryStat: value = expectedStatRecord.PlayerSecondaryStat; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.PlayerSecondaryStatMod; break; case ExpectedStatType.ArmorConstant: value = expectedStatRecord.ArmorConstant; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.ArmorConstantMod; break; @@ -1396,7 +1398,7 @@ namespace Game.DataStorage case ExpectedStatType.CreatureSpellDamage: value = expectedStatRecord.CreatureSpellDamage; if (!contentTuningMods.Empty()) - value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat)); + value *= contentTuningMods.Sum(expectedStatMod => ExpectedStatModReducer(1.0f, expectedStatMod, stat, mythicPlusMilestoneSeason)); if (classMod != null) value *= classMod.CreatureSpellDamageMod; break; @@ -1440,169 +1442,6 @@ namespace Game.DataStorage return _heirlooms.LookupByKey(itemId); } - public List GetItemBonusList(uint bonusListId) - { - return _itemBonusLists.LookupByKey(bonusListId); - } - - public uint GetItemBonusListForItemLevelDelta(short delta) - { - return _itemLevelDeltaToBonusListContainer.LookupByKey(delta); - } - - void VisitItemBonusTree(uint itemBonusTreeId, bool visitChildren, Action visitor) - { - var bonusTreeNodeList = _itemBonusTrees.LookupByKey(itemBonusTreeId); - if (bonusTreeNodeList.Empty()) - return; - - foreach (var bonusTreeNode in bonusTreeNodeList) - { - visitor(bonusTreeNode); - if (visitChildren && bonusTreeNode.ChildItemBonusTreeID != 0) - VisitItemBonusTree(bonusTreeNode.ChildItemBonusTreeID, true, visitor); - } - } - - public List GetDefaultItemBonusTree(uint itemId, ItemContext itemContext) - { - List bonusListIDs = new(); - - ItemSparseRecord proto = ItemSparseStorage.LookupByKey(itemId); - if (proto == null) - return bonusListIDs; - - var itemIdRange = _itemToBonusTree.LookupByKey(itemId); - if (itemIdRange == null) - return bonusListIDs; - - ushort itemLevelSelectorId = 0; - foreach (var itemBonusTreeId in itemIdRange) - { - uint matchingNodes = 0; - VisitItemBonusTree(itemBonusTreeId, false, bonusTreeNode => - { - if ((ItemContext)bonusTreeNode.ItemContext == ItemContext.None || itemContext == (ItemContext)bonusTreeNode.ItemContext) - ++matchingNodes; - }); - - if (matchingNodes != 1) - continue; - - VisitItemBonusTree(itemBonusTreeId, true, bonusTreeNode => - { - ItemContext requiredContext = (ItemContext)bonusTreeNode.ItemContext != ItemContext.ForceToNone ? (ItemContext)bonusTreeNode.ItemContext : ItemContext.None; - if ((ItemContext)bonusTreeNode.ItemContext != ItemContext.None && itemContext != requiredContext) - return; - - if (bonusTreeNode.ChildItemBonusListID != 0) - { - bonusListIDs.Add(bonusTreeNode.ChildItemBonusListID); - } - else if (bonusTreeNode.ChildItemLevelSelectorID != 0) - { - itemLevelSelectorId = bonusTreeNode.ChildItemLevelSelectorID; - } - }); - } - ItemLevelSelectorRecord selector = ItemLevelSelectorStorage.LookupByKey(itemLevelSelectorId); - if (selector != null) - { - short delta = (short)(selector.MinItemLevel - proto.ItemLevel); - - uint bonus = GetItemBonusListForItemLevelDelta(delta); - if (bonus != 0) - bonusListIDs.Add(bonus); - - ItemLevelSelectorQualitySetRecord selectorQualitySet = ItemLevelSelectorQualitySetStorage.LookupByKey(selector.ItemLevelSelectorQualitySetID); - if (selectorQualitySet != null) - { - var itemSelectorQualities = _itemLevelQualitySelectorQualities.LookupByKey(selector.ItemLevelSelectorQualitySetID); - if (itemSelectorQualities != null) - { - ItemQuality quality = ItemQuality.Uncommon; - if (selector.MinItemLevel >= selectorQualitySet.IlvlEpic) - quality = ItemQuality.Epic; - else if (selector.MinItemLevel >= selectorQualitySet.IlvlRare) - quality = ItemQuality.Rare; - - var itemSelectorQuality = itemSelectorQualities.Find(p => p.Quality < (sbyte)quality); - - if (itemSelectorQuality != null) - bonusListIDs.Add(itemSelectorQuality.QualityItemBonusListID); - } - } - - AzeriteUnlockMappingRecord azeriteUnlockMapping = _azeriteUnlockMappings.LookupByKey((proto.Id, itemContext)); - if (azeriteUnlockMapping != null) - { - switch (proto.inventoryType) - { - case InventoryType.Head: - bonusListIDs.Add(azeriteUnlockMapping.ItemBonusListHead); - break; - case InventoryType.Shoulders: - bonusListIDs.Add(azeriteUnlockMapping.ItemBonusListShoulders); - break; - case InventoryType.Chest: - case InventoryType.Robe: - bonusListIDs.Add(azeriteUnlockMapping.ItemBonusListChest); - break; - } - } - } - - return bonusListIDs; - } - - public List GetAllItemBonusTreeBonuses(uint itemBonusTreeId) - { - List bonusListIDs = new(); - VisitItemBonusTree(itemBonusTreeId, true, bonusTreeNode => - { - if (bonusTreeNode.ChildItemBonusListID != 0) - bonusListIDs.Add(bonusTreeNode.ChildItemBonusListID); - }); - return bonusListIDs; - } - - void LoadAzeriteEmpoweredItemUnlockMappings(MultiMap azeriteUnlockMappingsBySet, uint itemId) - { - var itemIdRange = _itemToBonusTree.LookupByKey(itemId); - if (itemIdRange == null) - return; - - foreach (var itemTreeItr in itemIdRange) - { - VisitItemBonusTree(itemTreeItr, true, bonusTreeNode => - { - if (bonusTreeNode.ChildItemBonusListID == 0 && bonusTreeNode.ChildItemLevelSelectorID != 0) - { - ItemLevelSelectorRecord selector = ItemLevelSelectorStorage.LookupByKey(bonusTreeNode.ChildItemLevelSelectorID); - if (selector == null) - return; - - var azeriteUnlockMappings = azeriteUnlockMappingsBySet.LookupByKey(selector.AzeriteUnlockMappingSet); - if (azeriteUnlockMappings != null) - { - AzeriteUnlockMappingRecord selectedAzeriteUnlockMapping = null; - foreach (AzeriteUnlockMappingRecord azeriteUnlockMapping in azeriteUnlockMappings) - { - if (azeriteUnlockMapping.ItemLevel > selector.MinItemLevel || - (selectedAzeriteUnlockMapping != null && selectedAzeriteUnlockMapping.ItemLevel > azeriteUnlockMapping.ItemLevel)) - continue; - - selectedAzeriteUnlockMapping = azeriteUnlockMapping; - } - - if (selectedAzeriteUnlockMapping != null) - _azeriteUnlockMappings[(itemId, (ItemContext)bonusTreeNode.ItemContext)] = selectedAzeriteUnlockMapping; - } - } - }); - } - } - public ItemChildEquipmentRecord GetItemChildEquipment(uint itemId) { return _itemChildEquipment.LookupByKey(itemId); @@ -2383,7 +2222,6 @@ namespace Game.DataStorage AzeriteItemMilestonePowerRecord[] _azeriteItemMilestonePowerByEssenceSlot = new AzeriteItemMilestonePowerRecord[SharedConst.MaxAzeriteEssenceSlot]; MultiMap _azeritePowers = new(); Dictionary<(uint azeriteUnlockSetId, ItemContext itemContext), byte[]> _azeriteTierUnlockLevels = new(); - Dictionary<(uint itemId, ItemContext itemContext), AzeriteUnlockMappingRecord> _azeriteUnlockMappings = new(); Dictionary<(int broadcastTextId, CascLocaleBit cascLocaleBit), int> _broadcastTextDurations = new(); ChrClassUIDisplayRecord[] _uiDisplayByClass = new ChrClassUIDisplayRecord[(int)Class.Max]; uint[][] _powersByClass = new uint[(int)Class.Max][]; @@ -2393,6 +2231,8 @@ namespace Game.DataStorage MultiMap, ChrCustomizationOptionRecord> _chrCustomizationOptionsByRaceAndGender = new(); Dictionary> _chrCustomizationRequiredChoices = new(); ChrSpecializationRecord[][] _chrSpecializationsByIndex = new ChrSpecializationRecord[(int)Class.Max + 1][]; + MultiMap _conditionalContentTuning = new(); + List<(uint, int)> _contentTuningLabels = new(); MultiMap _currencyContainers = new(); MultiMap _curvePoints = new(); Dictionary, EmotesTextSoundRecord> _emoteTextSounds = new(); @@ -2403,16 +2243,11 @@ namespace Game.DataStorage Dictionary _heirlooms = new(); MultiMap _glyphBindableSpells = new(); MultiMap _glyphRequiredSpecs = new(); - MultiMap _itemBonusLists = new(); - Dictionary _itemLevelDeltaToBonusListContainer = new(); - MultiMap _itemBonusTrees = new(); Dictionary _itemChildEquipment = new(); ItemClassRecord[] _itemClassByOldEnum = new ItemClassRecord[20]; List _itemsWithCurrencyCost = new(); MultiMap _itemCategoryConditions = new(); - MultiMap _itemLevelQualitySelectorQualities = new(); Dictionary _itemModifiedAppearancesByItem = new(); - MultiMap _itemToBonusTree = new(); MultiMap _itemSetSpells = new(); MultiMap _itemSpecOverrides = new(); List _journalTiersByIndex = new(); diff --git a/Source/Game/DataStorage/Structs/C_Records.cs b/Source/Game/DataStorage/Structs/C_Records.cs index 892c6c757..462392b0a 100644 --- a/Source/Game/DataStorage/Structs/C_Records.cs +++ b/Source/Game/DataStorage/Structs/C_Records.cs @@ -16,6 +16,18 @@ namespace Game.DataStorage public uint ChallengeOrigin; } + public sealed class ChallengeModeItemBonusOverrideRecord + { + public uint Id; + public int ItemBonusTreeGroupID; + public int DstItemBonusTreeID; + public sbyte Type; + public int Value; + public int MythicPlusSeasonID; + public int PvPSeasonID; + public uint SrcItemBonusTreeID; + } + public sealed class CharTitlesRecord { public uint Id; @@ -317,6 +329,15 @@ namespace Game.DataStorage public ushort[] Camera = new ushort[8]; } + public sealed class ConditionalContentTuningRecord + { + public uint Id; + public int OrderIndex; + public int RedirectContentTuningID; + public int RedirectFlag; + public uint ParentContentTuningID; + } + public sealed class ContentTuningRecord { public uint Id; @@ -357,6 +378,13 @@ namespace Game.DataStorage public uint ContentTuningID; } + public sealed class ContentTuningXLabelRecord + { + public uint Id; + public int LabelID; + public uint ContentTuningID; + } + public sealed class ConversationLineRecord { public uint Id; diff --git a/Source/Game/DataStorage/Structs/I_Records.cs b/Source/Game/DataStorage/Structs/I_Records.cs index 55aaeb88e..4956fb0c4 100644 --- a/Source/Game/DataStorage/Structs/I_Records.cs +++ b/Source/Game/DataStorage/Structs/I_Records.cs @@ -2,6 +2,7 @@ // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; +using System; namespace Game.DataStorage { @@ -99,12 +100,14 @@ namespace Game.DataStorage public sealed class ItemBonusListGroupEntryRecord { public uint Id; - public uint ItemBonusListGroupID; + public int ItemBonusListGroupID; public int ItemBonusListID; public int ItemLevelSelectorID; - public int OrderIndex; + public int SequenceValue; public int ItemExtendedCostID; public int PlayerConditionID; + public int Flags; + public int ItemLogicalCostGroupID; } public sealed class ItemBonusListLevelDeltaRecord @@ -120,6 +123,13 @@ namespace Game.DataStorage public int ItemID; } + public sealed class ItemBonusTreeRecord + { + public uint Id; + public int Flags; + public int InventoryTypeSlotMask; + } + public sealed class ItemBonusTreeNodeRecord { public uint Id; @@ -129,8 +139,8 @@ namespace Game.DataStorage public ushort ChildItemLevelSelectorID; public uint ChildItemBonusListGroupID; public uint IblGroupPointsModSetID; - public int Unknown1010_1; - public int Unknown1010_2; + public int MinMythicPlusLevel; + public int MaxMythicPlusLevel; public uint ParentItemBonusTreeID; } @@ -151,11 +161,24 @@ namespace Game.DataStorage public byte Flags; } + public sealed class ItemContextPickerEntryRecord + { + public uint Id; + public byte ItemCreationContext; + public byte OrderIndex; + public int PVal; + public int LabelID; + public uint Flags; + public uint PlayerConditionID; + public uint ItemContextPickerID; + } + public sealed class ItemCurrencyCostRecord { public uint Id; public uint ItemID; } + // common struct for: // ItemDamageAmmo.dbc // ItemDamageOneHand.dbc @@ -220,12 +243,16 @@ namespace Game.DataStorage public ushort AzeriteUnlockMappingSet; } - public sealed class ItemLevelSelectorQualityRecord + public sealed class ItemLevelSelectorQualityRecord : IEquatable, IEquatable { public uint Id; public uint QualityItemBonusListID; public sbyte Quality; public uint ParentILSQualitySetID; + + public bool Equals(ItemLevelSelectorQualityRecord other) { return Quality < other.Quality; } + + public bool Equals(ItemQuality quality) { return Quality < (sbyte)quality; } } public sealed class ItemLevelSelectorQualitySetRecord diff --git a/Source/Game/DataStorage/Structs/M_Records.cs b/Source/Game/DataStorage/Structs/M_Records.cs index 1ce01f27a..323c95536 100644 --- a/Source/Game/DataStorage/Structs/M_Records.cs +++ b/Source/Game/DataStorage/Structs/M_Records.cs @@ -229,4 +229,12 @@ namespace Game.DataStorage public uint SubtitleFileDataID; public int SubtitleFileFormat; } + + public sealed class MythicPlusSeasonRecord + { + public uint Id; + public int MilestoneSeason; + public int ExpansionLevel; + public int HeroicLFGDungeonMinGear; + } } diff --git a/Source/Game/DataStorage/Structs/P_Records.cs b/Source/Game/DataStorage/Structs/P_Records.cs index 1cc772dfd..46606f694 100644 --- a/Source/Game/DataStorage/Structs/P_Records.cs +++ b/Source/Game/DataStorage/Structs/P_Records.cs @@ -178,6 +178,14 @@ namespace Game.DataStorage public byte ItemLevelDelta; } + public sealed class PvpSeasonRecord + { + public uint Id; + public int MilestoneSeason; + public int AllianceAchievementID; + public int HordeAchievementID; + } + public sealed class PvpTalentRecord { public string Description; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index e02cd6c76..7b1207362 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -2715,7 +2715,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); - float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); + float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass, 0); return (ulong)Math.Max(baseHealth * creatureDifficulty.HealthModifier, 1.0f); } @@ -2736,7 +2736,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); - return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); + return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass, 0); } public override float GetDamageMultiplierForTarget(WorldObject target) @@ -2753,7 +2753,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); - float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); + float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass, 0); return baseArmor * creatureDifficulty.ArmorModifier; } diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index 8118489d9..32927aa34 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -969,10 +969,11 @@ namespace Game.Entities Loot fishLoot = new(GetMap(), GetGUID(), LootType.Fishing, null); uint areaId = GetAreaId(); + ItemContext itemContext = ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), lootOwner); AreaTableRecord areaEntry; while ((areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId)) != null) { - fishLoot.FillLoot(areaId, LootStorage.Fishing, lootOwner, true, true); + fishLoot.FillLoot(areaId, LootStorage.Fishing, lootOwner, true, true, LootModes.Default, itemContext); if (!fishLoot.IsLooted()) break; @@ -980,7 +981,7 @@ namespace Game.Entities } if (fishLoot.IsLooted()) - fishLoot.FillLoot(defaultzone, LootStorage.Fishing, lootOwner, true, true); + fishLoot.FillLoot(defaultzone, LootStorage.Fishing, lootOwner, true, true, LootModes.Default, itemContext); return fishLoot; } @@ -992,10 +993,11 @@ namespace Game.Entities Loot fishLoot = new(GetMap(), GetGUID(), LootType.FishingJunk, null); uint areaId = GetAreaId(); + ItemContext itemContext = ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), lootOwner); AreaTableRecord areaEntry; while ((areaEntry = CliDB.AreaTableStorage.LookupByKey(areaId)) != null) { - fishLoot.FillLoot(areaId, LootStorage.Fishing, lootOwner, true, true, LootModes.JunkFish); + fishLoot.FillLoot(areaId, LootStorage.Fishing, lootOwner, true, true, LootModes.JunkFish, itemContext); if (!fishLoot.IsLooted()) break; @@ -1003,7 +1005,7 @@ namespace Game.Entities } if (fishLoot.IsLooted()) - fishLoot.FillLoot(defaultzone, LootStorage.Fishing, lootOwner, true, true, LootModes.JunkFish); + fishLoot.FillLoot(defaultzone, LootStorage.Fishing, lootOwner, true, true, LootModes.JunkFish, itemContext); return fishLoot; } @@ -1692,7 +1694,7 @@ namespace Game.Entities loot = new Loot(GetMap(), GetGUID(), LootType.Chest, groupRules ? group : null); loot.SetDungeonEncounterId(info.Chest.DungeonEncounter); - loot.FillLoot(info.GetLootId(), LootStorage.Gameobject, player, !groupRules, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); + loot.FillLoot(info.GetLootId(), LootStorage.Gameobject, player, !groupRules, false, GetLootMode(), ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), player)); if (GetLootMode() > 0) { @@ -1731,7 +1733,7 @@ namespace Game.Entities m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(info.Chest.DungeonEncounter, info.Chest.chestPersonalLoot, LootStorage.Gameobject, LootType.Chest, this, addon != null ? addon.Mingold : 0, addon != null ? addon.Maxgold : 0, - (ushort)GetLootMode(), GetMap().GetDifficultyLootItemContext(), tappers); + (ushort)GetLootMode(), GetMap().GetMapDifficulty(), tappers); } else { @@ -1739,7 +1741,7 @@ namespace Game.Entities m_personalLoot[player.GetGUID()] = loot; loot.SetDungeonEncounterId(info.Chest.DungeonEncounter); - loot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); + loot.FillLoot(info.Chest.chestPersonalLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), player)); if (GetLootMode() > 0 && addon != null) loot.GenerateMoneyLoot(addon.Mingold, addon.Maxgold); @@ -1752,7 +1754,7 @@ namespace Game.Entities if (info.Chest.chestPushLoot != 0) { Loot pushLoot = new(GetMap(), GetGUID(), LootType.Chest, null); - pushLoot.FillLoot(info.Chest.chestPushLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); + pushLoot.FillLoot(info.Chest.chestPushLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), player)); pushLoot.AutoStore(player, ItemConst.NullBag, ItemConst.NullSlot); } @@ -2268,7 +2270,7 @@ namespace Game.Entities Player player = user.ToPlayer(); Loot loot = new Loot(GetMap(), GetGUID(), LootType.Fishinghole, null); - loot.FillLoot(GetGoInfo().GetLootId(), LootStorage.Gameobject, player, true); + loot.FillLoot(GetGoInfo().GetLootId(), LootStorage.Gameobject, player, true, false, LootModes.Default, ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), player)); m_personalLoot[player.GetGUID()] = loot; player.SendLoot(loot); @@ -2452,7 +2454,7 @@ namespace Game.Entities Loot newLoot = new(GetMap(), GetGUID(), LootType.Chest, null); m_personalLoot[player.GetGUID()] = newLoot; - newLoot.FillLoot(info.GatheringNode.chestLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), GetMap().GetDifficultyLootItemContext()); + newLoot.FillLoot(info.GatheringNode.chestLoot, LootStorage.Gameobject, player, true, false, GetLootMode(), ItemBonusMgr.GetContextForPlayer(GetMap().GetMapDifficulty(), player)); } if (info.GatheringNode.triggeredEvent != 0) diff --git a/Source/Game/Entities/Item/Item.cs b/Source/Game/Entities/Item/Item.cs index 6dceaa960..740258b3f 100644 --- a/Source/Game/Entities/Item/Item.cs +++ b/Source/Game/Entities/Item/Item.cs @@ -1086,26 +1086,19 @@ namespace Game.Entities { case ItemEnchantmentType.BonusListID: { - var bonusesEffect = Global.DB2Mgr.GetItemBonusList(gemEnchant.EffectArg[i]); - if (bonusesEffect != null) - { - foreach (ItemBonusRecord itemBonus in bonusesEffect) - if (itemBonus.BonusType == ItemBonusType.ItemLevel) - - _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; - } + foreach (var itemBonus in ItemBonusMgr.GetItemBonuses(gemEnchant.EffectArg[i])) + if (itemBonus.BonusType == ItemBonusType.ItemLevel) + _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; break; } case ItemEnchantmentType.BonusListCurve: { - uint artifactrBonusListId = Global.DB2Mgr.GetItemBonusListForItemLevelDelta((short)Global.DB2Mgr.GetCurveValueAt((uint)Curves.ArtifactRelicItemLevelBonus, gemBaseItemLevel + gemBonus.ItemLevelBonus)); - if (artifactrBonusListId != 0) + uint bonusListId = ItemBonusMgr.GetItemBonusListForItemLevelDelta((short)Global.DB2Mgr.GetCurveValueAt((uint)Curves.ArtifactRelicItemLevelBonus, gemBaseItemLevel + gemBonus.ItemLevelBonus)); + if (bonusListId != 0) { - var bonusesEffect = Global.DB2Mgr.GetItemBonusList(artifactrBonusListId); - if (bonusesEffect != null) - foreach (ItemBonusRecord itemBonus in bonusesEffect) - if (itemBonus.BonusType == ItemBonusType.ItemLevel) - _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; + foreach (var itemBonus in ItemBonusMgr.GetItemBonuses(bonusListId)) + if (itemBonus.BonusType == ItemBonusType.ItemLevel) + _bonusData.GemItemLevelBonus[slot] += (uint)itemBonus.Value[0]; } break; } @@ -1195,22 +1188,25 @@ namespace Game.Entities owner.SendPacket(itemTimeUpdate); } - public static Item CreateItem(uint item, uint count, ItemContext context, Player player = null) + public static Item CreateItem(uint itemEntry, uint count, ItemContext context, Player player = null, bool addDefaultBonuses = true) { if (count < 1) return null; //don't create item at zero count - var pProto = Global.ObjectMgr.GetItemTemplate(item); + var pProto = Global.ObjectMgr.GetItemTemplate(itemEntry); if (pProto != null) { if (count > pProto.GetMaxStackSize()) count = pProto.GetMaxStackSize(); - Item pItem = Bag.NewItemOrBag(pProto); - if (pItem.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), item, context, player)) + Item item = Bag.NewItemOrBag(pProto); + if (item.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), itemEntry, context, player)) { - pItem.SetCount(count); - return pItem; + item.SetCount(count); + if (addDefaultBonuses) + item.SetBonuses(ItemBonusMgr.GetBonusListsForItem(itemEntry, new(context))); + + return item; } } @@ -1219,7 +1215,7 @@ namespace Game.Entities public Item CloneItem(uint count, Player player = null) { - Item newItem = CreateItem(GetEntry(), count, GetContext(), player); + Item newItem = CreateItem(GetEntry(), count, GetContext(), player, false); if (newItem == null) return null; @@ -1227,6 +1223,7 @@ namespace Game.Entities newItem.SetGiftCreator(GetGiftCreator()); newItem.ReplaceAllItemFlags((ItemFieldFlags)(m_itemData.DynamicFlags & ~(uint)(ItemFieldFlags.Refundable | ItemFieldFlags.BopTradeable))); newItem.SetExpiration(m_itemData.Expiration); + newItem.SetBonuses(m_itemData.ItemBonusKey.GetValue().BonusListIDs); // player CAN be NULL in which case we must not update random properties because that accesses player's item update queue if (player != null) newItem.SetItemRandomBonusList(m_randomBonusListId); @@ -2072,26 +2069,21 @@ namespace Game.Entities } public List GetBonusListIDs() { return m_itemData.ItemBonusKey.GetValue().BonusListIDs; } - + public void AddBonuses(uint bonusListID) { var bonusListIDs = GetBonusListIDs(); if (bonusListIDs.Contains(bonusListID)) return; - var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListID); - if (bonuses != null) - { - ItemBonusKey itemBonusKey = new(); - itemBonusKey.ItemID = GetEntry(); - itemBonusKey.BonusListIDs = GetBonusListIDs(); - itemBonusKey.BonusListIDs.Add(bonusListID); - SetUpdateFieldValue(m_values.ModifyValue(m_itemData).ModifyValue(m_itemData.ItemBonusKey), itemBonusKey); - foreach (ItemBonusRecord bonus in bonuses) - _bonusData.AddBonus(bonus.BonusType, bonus.Value); - - SetUpdateFieldValue(m_values.ModifyValue(m_itemData).ModifyValue(m_itemData.ItemAppearanceModID), (byte)_bonusData.AppearanceModID); - } + ItemBonusKey itemBonusKey = new(); + itemBonusKey.ItemID = GetEntry(); + itemBonusKey.BonusListIDs = GetBonusListIDs(); + itemBonusKey.BonusListIDs.Add(bonusListID); + SetUpdateFieldValue(m_values.ModifyValue(m_itemData).ModifyValue(m_itemData.ItemBonusKey), itemBonusKey); + foreach (var bonus in ItemBonusMgr.GetItemBonuses(bonusListID)) + _bonusData.AddBonus(bonus.BonusType, bonus.Value); + SetUpdateFieldValue(m_values.ModifyValue(m_itemData).ModifyValue(m_itemData.ItemAppearanceModID), (byte)_bonusData.AppearanceModID); } public void SetBonuses(List bonusListIDs) @@ -2906,10 +2898,8 @@ namespace Game.Entities public void AddBonusList(uint bonusListId) { - var bonuses = Global.DB2Mgr.GetItemBonusList(bonusListId); - if (bonuses != null) - foreach (ItemBonusRecord bonus in bonuses) - AddBonus(bonus.BonusType, bonus.Value); + foreach (var bonus in ItemBonusMgr.GetItemBonuses(bonusListId)) + AddBonus(bonus.BonusType, bonus.Value); } public void AddBonus(ItemBonusType type, int[] values) diff --git a/Source/Game/Entities/Item/ItemBonusManager.cs b/Source/Game/Entities/Item/ItemBonusManager.cs new file mode 100644 index 000000000..6653d4daf --- /dev/null +++ b/Source/Game/Entities/Item/ItemBonusManager.cs @@ -0,0 +1,506 @@ +// Copyright (c) CypherCore All rights reserved. +// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. + +using Framework.Constants; +using Game.DataStorage; +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Game.Entities +{ + class ItemBonusMgr + { + static MultiMap _azeriteUnlockMappings = new(); + static MultiMap _challengeModeItemBonusOverrides = new(); + static MultiMap _itemBonusLists = new(); + static MultiMap _itemBonusListGroupEntries = new(); + static Dictionary _itemLevelDeltaToBonusListContainer = new(); + static SortedMultiMap _itemLevelQualitySelectorQualities = new(); + static MultiMap _itemBonusTrees = new(); + static MultiMap _itemToBonusTree = new(); + + public static void Load() + { + foreach (var azeriteUnlockMapping in CliDB.AzeriteUnlockMappingStorage.Values) + _azeriteUnlockMappings.Add(azeriteUnlockMapping.AzeriteUnlockMappingSetID, azeriteUnlockMapping); + + foreach (var challengeModeItemBonusOverride in CliDB.ChallengeModeItemBonusOverrideStorage.Values) + _challengeModeItemBonusOverrides.Add(challengeModeItemBonusOverride.SrcItemBonusTreeID, challengeModeItemBonusOverride); + + foreach (var bonus in CliDB.ItemBonusStorage.Values) + _itemBonusLists.Add(bonus.ParentItemBonusListID, bonus); + + foreach (var bonusListGroupEntry in CliDB.ItemBonusListGroupEntryStorage.Values) + _itemBonusListGroupEntries.Add(bonusListGroupEntry.ItemBonusListGroupID, bonusListGroupEntry); + + foreach (var itemBonusListLevelDelta in CliDB.ItemBonusListLevelDeltaStorage.Values) + _itemLevelDeltaToBonusListContainer[itemBonusListLevelDelta.ItemLevelDelta] = itemBonusListLevelDelta.Id; + + foreach (var itemLevelSelectorQuality in CliDB.ItemLevelSelectorQualityStorage.Values) + _itemLevelQualitySelectorQualities.Add(itemLevelSelectorQuality.ParentILSQualitySetID, itemLevelSelectorQuality); + + foreach (var bonusTreeNode in CliDB.ItemBonusTreeNodeStorage.Values) + _itemBonusTrees.Add(bonusTreeNode.ParentItemBonusTreeID, bonusTreeNode); + + foreach (var itemBonusTreeAssignment in CliDB.ItemXBonusTreeStorage.Values) + _itemToBonusTree.Add(itemBonusTreeAssignment.ItemID, itemBonusTreeAssignment.ItemBonusTreeID); + } + + public static ItemContext GetContextForPlayer(MapDifficultyRecord mapDifficulty, Player player) + { + ItemContext evalContext(ItemContext currentContext, ItemContext newContext) + { + if (newContext == ItemContext.None) + newContext = currentContext; + else if (newContext == ItemContext.ForceToNone) + newContext = ItemContext.None; + return newContext; + } + + ItemContext context = ItemContext.None; + var difficulty = CliDB.DifficultyStorage.LookupByKey(mapDifficulty.DifficultyID); + if (difficulty != null) + context = evalContext(context, (ItemContext)difficulty.ItemContext); + + context = evalContext(context, (ItemContext)mapDifficulty.ItemContext); + + if (mapDifficulty.ItemContextPickerID != 0) + { + uint contentTuningId = Global.DB2Mgr.GetRedirectedContentTuningId((uint)mapDifficulty.ContentTuningID, player.m_playerData.CtrOptions.GetValue().ContentTuningConditionMask); + + ItemContextPickerEntryRecord selectedPickerEntry = null; + foreach (var itemContextPickerEntry in CliDB.ItemContextPickerEntryStorage.Values) + { + if (itemContextPickerEntry.ItemContextPickerID != mapDifficulty.ItemContextPickerID) + continue; + + if (itemContextPickerEntry.PVal <= 0) + continue; + + bool meetsPlayerCondition = false; + if (player) + { + var playerCondition = CliDB.PlayerConditionStorage.LookupByKey(itemContextPickerEntry.PlayerConditionID); + if (playerCondition != null) + meetsPlayerCondition = ConditionManager.IsPlayerMeetingCondition(player, playerCondition); + } + + if ((itemContextPickerEntry.Flags & 0x1) != 0) + meetsPlayerCondition = !meetsPlayerCondition; + + if (!meetsPlayerCondition) + continue; + + if (itemContextPickerEntry.LabelID != 0 && !Global.DB2Mgr.HasContentTuningLabel(contentTuningId, itemContextPickerEntry.LabelID)) + continue; + + if (selectedPickerEntry == null || selectedPickerEntry.OrderIndex < itemContextPickerEntry.OrderIndex) + selectedPickerEntry = itemContextPickerEntry; + } + + if (selectedPickerEntry != null) + context = evalContext(context, (ItemContext)selectedPickerEntry.ItemCreationContext); + } + + return context; + } + + public static List GetItemBonuses(uint bonusListId) + { + return _itemBonusLists.LookupByKey(bonusListId); + } + + public static uint GetItemBonusListForItemLevelDelta(short delta) + { + return _itemLevelDeltaToBonusListContainer.LookupByKey(delta); + } + + public static bool CanApplyBonusTreeToItem(ItemTemplate itemTemplate, uint itemBonusTreeId, ItemBonusGenerationParams generationParams) + { + var bonusTree = CliDB.ItemBonusTreeStorage.LookupByKey(itemBonusTreeId); + if (bonusTree != null) + { + if (bonusTree.InventoryTypeSlotMask != 0) + if ((1 << (int)itemTemplate.GetInventoryType() & bonusTree.InventoryTypeSlotMask) == 0) + return false; + + if ((bonusTree.Flags & 0x8) != 0 && !itemTemplate.HasFlag(ItemFlags2.CasterWeapon)) + return false; + if ((bonusTree.Flags & 0x10) != 0 && itemTemplate.HasFlag(ItemFlags2.CasterWeapon)) + return false; + if ((bonusTree.Flags & 0x20) != 0 && !itemTemplate.HasFlag(ItemFlags4.CcTrinket)) + return false; + if ((bonusTree.Flags & 0x40) != 0 && itemTemplate.HasFlag(ItemFlags4.CcTrinket)) + return false; + + if ((bonusTree.Flags & 0x4) != 0) + return true; + } + + var bonusTreeNodes = _itemBonusTrees.LookupByKey(itemBonusTreeId); + if (!bonusTreeNodes.Empty()) + { + bool anyNodeMatched = false; + foreach (var bonusTreeNode in bonusTreeNodes) + { + if (bonusTreeNode.MinMythicPlusLevel > 0) + continue; + + ItemContext nodeContext = (ItemContext)bonusTreeNode.ItemContext; + if (nodeContext == ItemContext.None || nodeContext == generationParams.Context) + { + if (anyNodeMatched) + return false; + + anyNodeMatched = true; + } + } + } + + return true; + } + + public static uint GetBonusTreeIdOverride(uint itemBonusTreeId, ItemBonusGenerationParams generationParams) + { + // TODO: configure seasons globally + var mythicPlusSeason = CliDB.MythicPlusSeasonStorage.LookupByKey(0); + if (mythicPlusSeason != null) + { + int selectedLevel = -1; + int selectedMilestoneSeason = -1; + ChallengeModeItemBonusOverrideRecord selectedItemBonusOverride = null; + foreach (var itemBonusOverride in _challengeModeItemBonusOverrides.LookupByKey(itemBonusTreeId)) + { + if (itemBonusOverride.Type != 0) + continue; + + if (itemBonusOverride.Value > generationParams.MythicPlusKeystoneLevel.GetValueOrDefault(-1)) + continue; + + if (itemBonusOverride.MythicPlusSeasonID != 0) + { + var overrideSeason = CliDB.MythicPlusSeasonStorage.LookupByKey(itemBonusOverride.MythicPlusSeasonID); + if (overrideSeason == null) + continue; + + if (mythicPlusSeason.MilestoneSeason < overrideSeason.MilestoneSeason) + continue; + + if (selectedMilestoneSeason > overrideSeason.MilestoneSeason) + continue; + + if (selectedMilestoneSeason == overrideSeason.MilestoneSeason) + if (selectedLevel > itemBonusOverride.Value) + continue; + + selectedMilestoneSeason = overrideSeason.MilestoneSeason; + } + else if (selectedLevel > itemBonusOverride.Value) + continue; + + selectedLevel = itemBonusOverride.Value; + selectedItemBonusOverride = itemBonusOverride; + } + + if (selectedItemBonusOverride != null && selectedItemBonusOverride.DstItemBonusTreeID != 0) + itemBonusTreeId = (uint)selectedItemBonusOverride.DstItemBonusTreeID; + } + + // TODO: configure seasons globally + var pvpSeason = CliDB.PvpSeasonStorage.LookupByKey(0); + if (pvpSeason != null) + { + int selectedLevel = -1; + int selectedMilestoneSeason = -1; + ChallengeModeItemBonusOverrideRecord selectedItemBonusOverride = null; + foreach (var itemBonusOverride in _challengeModeItemBonusOverrides.LookupByKey(itemBonusTreeId)) + { + if (itemBonusOverride.Type != 1) + continue; + + if (itemBonusOverride.Value > generationParams.PvpTier.GetValueOrDefault(-1)) + continue; + + if (itemBonusOverride.PvPSeasonID != 0) + { + var overrideSeason = CliDB.PvpSeasonStorage.LookupByKey(itemBonusOverride.PvPSeasonID); + if (overrideSeason == null) + continue; + + if (pvpSeason.MilestoneSeason < overrideSeason.MilestoneSeason) + continue; + + if (selectedMilestoneSeason > overrideSeason.MilestoneSeason) + continue; + + if (selectedMilestoneSeason == overrideSeason.MilestoneSeason) + if (selectedLevel > itemBonusOverride.Value) + continue; + + selectedMilestoneSeason = overrideSeason.MilestoneSeason; + } + else if (selectedLevel > itemBonusOverride.Value) + continue; + + selectedLevel = itemBonusOverride.Value; + selectedItemBonusOverride = itemBonusOverride; + } + + if (selectedItemBonusOverride != null && selectedItemBonusOverride.DstItemBonusTreeID != 0) + itemBonusTreeId = (uint)selectedItemBonusOverride.DstItemBonusTreeID; + } + + return itemBonusTreeId; + } + + public static void ApplyBonusTreeHelper(ItemTemplate itemTemplate, uint itemBonusTreeId, ItemBonusGenerationParams generationParams, int sequenceLevel, ref uint itemLevelSelectorId, List bonusListIDs) + { + uint originalItemBonusTreeId = itemBonusTreeId; + + // override bonus tree with season specific values + itemBonusTreeId = GetBonusTreeIdOverride(itemBonusTreeId, generationParams); + + if (!CanApplyBonusTreeToItem(itemTemplate, itemBonusTreeId, generationParams)) + return; + + var treeList = _itemBonusTrees.LookupByKey(itemBonusTreeId); + if (treeList.Empty()) + return; + + foreach (var bonusTreeNode in treeList) + { + ItemContext nodeContext = (ItemContext)bonusTreeNode.ItemContext; + ItemContext requiredContext = nodeContext != ItemContext.ForceToNone ? nodeContext : ItemContext.None; + if (nodeContext != ItemContext.None && generationParams.Context != requiredContext) + continue; + + if (generationParams.MythicPlusKeystoneLevel != 0) + { + if (bonusTreeNode.MinMythicPlusLevel != 0 && generationParams.MythicPlusKeystoneLevel < bonusTreeNode.MinMythicPlusLevel) + continue; + + if (bonusTreeNode.MaxMythicPlusLevel != 0 && generationParams.MythicPlusKeystoneLevel > bonusTreeNode.MaxMythicPlusLevel) + continue; + } + + if (bonusTreeNode.ChildItemBonusTreeID != 0) + ApplyBonusTreeHelper(itemTemplate, bonusTreeNode.ChildItemBonusTreeID, generationParams, sequenceLevel, ref itemLevelSelectorId, bonusListIDs); + else if (bonusTreeNode.ChildItemBonusListID != 0) + bonusListIDs.Add(bonusTreeNode.ChildItemBonusListID); + else if (bonusTreeNode.ChildItemLevelSelectorID != 0) + itemLevelSelectorId = bonusTreeNode.ChildItemLevelSelectorID; + else if (bonusTreeNode.ChildItemBonusListGroupID != 0) + { + int resolvedSequenceLevel = sequenceLevel; + switch (originalItemBonusTreeId) + { + case 4001: + resolvedSequenceLevel = 1; + break; + case 4079: + if (generationParams.MythicPlusKeystoneLevel != 0) + { + switch (bonusTreeNode.IblGroupPointsModSetID) + { + case 2909: // MythicPlus_End_of_Run levels 2-8 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(62951, generationParams.MythicPlusKeystoneLevel.Value); + break; + case 2910: // MythicPlus_End_of_Run levels 9-16 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(62952, generationParams.MythicPlusKeystoneLevel.Value); + break; + case 2911: // MythicPlus_End_of_Run levels 17-20 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(62954, generationParams.MythicPlusKeystoneLevel.Value); + break; + case 3007: // MythicPlus_Jackpot (weekly reward) levels 2-7 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(64388, generationParams.MythicPlusKeystoneLevel.Value); + break; + case 3008: // MythicPlus_Jackpot (weekly reward) levels 8-15 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(64389, generationParams.MythicPlusKeystoneLevel.Value); + break; + case 3009: // MythicPlus_Jackpot (weekly reward) levels 16-20 + resolvedSequenceLevel = (int)Global.DB2Mgr.GetCurveValueAt(64395, generationParams.MythicPlusKeystoneLevel.Value); + break; + default: + break; + } + } + break; + case 4125: + resolvedSequenceLevel = 2; + break; + case 4126: + resolvedSequenceLevel = 3; + break; + case 4127: + resolvedSequenceLevel = 4; + break; + case 4128: + switch (generationParams.Context) + { + case ItemContext.RaidNormal: + case ItemContext.RaidRaidFinder: + case ItemContext.RaidHeroic: + resolvedSequenceLevel = 2; + break; + case ItemContext.RaidMythic: + resolvedSequenceLevel = 6; + break; + default: + break; + } + break; + case 4140: + switch (generationParams.Context) + { + case ItemContext.DungeonNormal: + resolvedSequenceLevel = 2; + break; + case ItemContext.DungeonHeroic: + resolvedSequenceLevel = 4; + break; + default: + break; + } + break; + default: + break; + } + + foreach (var bonusListGroupEntry in _itemBonusListGroupEntries.LookupByKey(bonusTreeNode.ChildItemBonusListGroupID)) + { + if ((resolvedSequenceLevel > 0 || bonusListGroupEntry.SequenceValue <= 0) && resolvedSequenceLevel != bonusListGroupEntry.SequenceValue) + continue; + + itemLevelSelectorId = (uint)bonusListGroupEntry.ItemLevelSelectorID; + bonusListIDs.Add((uint)bonusListGroupEntry.ItemBonusListID); + break; + } + } + } + } + + public static uint GetAzeriteUnlockBonusList(ushort azeriteUnlockMappingSetId, ushort minItemLevel, InventoryType inventoryType) + { + AzeriteUnlockMappingRecord selectedAzeriteUnlockMapping = null; + foreach (var azeriteUnlockMapping in _azeriteUnlockMappings.LookupByKey(azeriteUnlockMappingSetId)) + { + if (minItemLevel < azeriteUnlockMapping.ItemLevel) + continue; + + if (selectedAzeriteUnlockMapping != null && selectedAzeriteUnlockMapping.ItemLevel > azeriteUnlockMapping.ItemLevel) + continue; + + selectedAzeriteUnlockMapping = azeriteUnlockMapping; + } + + if (selectedAzeriteUnlockMapping != null) + { + switch (inventoryType) + { + case InventoryType.Head: + return selectedAzeriteUnlockMapping.ItemBonusListHead; + case InventoryType.Shoulders: + return selectedAzeriteUnlockMapping.ItemBonusListShoulders; + case InventoryType.Chest: + case InventoryType.Robe: + return selectedAzeriteUnlockMapping.ItemBonusListChest; + default: + break; + } + } + + return 0; + } + + public static List GetBonusListsForItem(uint itemId, ItemBonusGenerationParams generationParams) + { + List bonusListIDs = new(); + + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + if (itemTemplate == null) + return bonusListIDs; + + uint itemLevelSelectorId = 0; + + foreach (var itemBonusTreeId in _itemToBonusTree.LookupByKey(itemId)) + ApplyBonusTreeHelper(itemTemplate, itemBonusTreeId, generationParams, 0, ref itemLevelSelectorId, bonusListIDs); + + var selector = CliDB.ItemLevelSelectorStorage.LookupByKey(itemLevelSelectorId); + if (selector != null) + { + short delta = (short)(selector.MinItemLevel - itemTemplate.GetBaseItemLevel()); + + uint bonus = GetItemBonusListForItemLevelDelta(delta); + if (bonus != 0) + bonusListIDs.Add(bonus); + + var selectorQualitySet = CliDB.ItemLevelSelectorQualitySetStorage.LookupByKey(selector.ItemLevelSelectorQualitySetID); + if (selectorQualitySet != null) + { + var itemSelectorQualities = _itemLevelQualitySelectorQualities.LookupByKey(selector.ItemLevelSelectorQualitySetID); + if (!itemSelectorQualities.Empty()) + { + ItemQuality quality = ItemQuality.Uncommon; + if (selector.MinItemLevel >= selectorQualitySet.IlvlEpic) + quality = ItemQuality.Epic; + else if (selector.MinItemLevel >= selectorQualitySet.IlvlRare) + quality = ItemQuality.Rare; + + var itemSelectorQuality = itemSelectorQualities.First(record => record.Quality < (sbyte)quality); + + if (itemSelectorQuality != null) + bonusListIDs.Add(itemSelectorQuality.QualityItemBonusListID); + } + } + + uint azeriteUnlockBonusListId = GetAzeriteUnlockBonusList(selector.AzeriteUnlockMappingSet, selector.MinItemLevel, itemTemplate.GetInventoryType()); + if (azeriteUnlockBonusListId != 0) + bonusListIDs.Add(azeriteUnlockBonusListId); + } + + return bonusListIDs; + } + + public static void VisitItemBonusTree(uint itemBonusTreeId, Action visitor) + { + var treeItr = _itemBonusTrees.LookupByKey(itemBonusTreeId); + if (treeItr.Empty()) + return; + + foreach (var bonusTreeNode in treeItr) + { + visitor(bonusTreeNode); + if (bonusTreeNode.ChildItemBonusTreeID != 0) + VisitItemBonusTree(bonusTreeNode.ChildItemBonusTreeID, visitor); + } + } + + public static List GetAllBonusListsForTree(uint itemBonusTreeId) + { + List bonusListIDs = new(); + VisitItemBonusTree(itemBonusTreeId, bonusTreeNode => + { + if (bonusTreeNode.ChildItemBonusListID != 0) + bonusListIDs.Add(bonusTreeNode.ChildItemBonusListID); + }); + + return bonusListIDs; + } + + public struct ItemBonusGenerationParams + { + public ItemBonusGenerationParams(ItemContext context, int? mythicPlusKeystoneLevel = null, int? pvpTier = null) + { + Context = context; + MythicPlusKeystoneLevel = mythicPlusKeystoneLevel; + PvpTier = pvpTier; + } + + public ItemContext Context; + public int? MythicPlusKeystoneLevel; + public int? PvpTier; + } + } +} diff --git a/Source/Game/Entities/Item/ItemEnchantment.cs b/Source/Game/Entities/Item/ItemEnchantment.cs index 7b72e23ba..72754aad8 100644 --- a/Source/Game/Entities/Item/ItemEnchantment.cs +++ b/Source/Game/Entities/Item/ItemEnchantment.cs @@ -32,7 +32,7 @@ namespace Game.Entities uint bonusListId = result.Read(1); float chance = result.Read(2); - if (Global.DB2Mgr.GetItemBonusList(bonusListId) == null) + if (ItemBonusMgr.GetItemBonuses(bonusListId).Empty()) { Log.outError(LogFilter.Sql, $"Bonus list {bonusListId} used in `item_random_bonus_list_template` by id {id} doesn't have exist in ItemBonus.db2"); continue; diff --git a/Source/Game/Entities/Player/Player.Combat.cs b/Source/Game/Entities/Player/Player.Combat.cs index ce83d90fb..0c720d6c6 100644 --- a/Source/Game/Entities/Player/Player.Combat.cs +++ b/Source/Game/Entities/Player/Player.Combat.cs @@ -337,7 +337,7 @@ namespace Game.Entities public override float GetBlockPercent(uint attackerLevel) { float blockArmor = (float)m_activePlayerData.ShieldBlock; - float armorConstant = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, attackerLevel, -2, 0, Class.None); + float armorConstant = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, attackerLevel, -2, 0, Class.None, 0); if ((blockArmor + armorConstant) == 0) return 0; diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index ac5f59a6e..eb0e254a1 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -1209,8 +1209,6 @@ namespace Game.Entities { Log.outDebug(LogFilter.Player, "STORAGE: Creating initial item, itemId = {0}, count = {1}", itemId, amount); - var bonusListIDs = Global.DB2Mgr.GetDefaultItemBonusTree(itemId, context); - InventoryResult msg; // attempt equip by one while (amount > 0) @@ -1219,8 +1217,7 @@ namespace Game.Entities if (msg != InventoryResult.Ok) break; - Item item = EquipNewItem(eDest, itemId, context, true); - item.SetBonuses(bonusListIDs); + EquipNewItem(eDest, itemId, context, true); AutoUnequipOffhandIfNeed(); --amount; } @@ -1234,7 +1231,7 @@ namespace Game.Entities msg = CanStoreNewItem(InventorySlots.Bag0, ItemConst.NullSlot, sDest, itemId, amount); if (msg == InventoryResult.Ok) { - StoreNewItem(sDest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, context, bonusListIDs); + StoreNewItem(sDest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, context); return true; // stored } @@ -1242,18 +1239,20 @@ namespace Game.Entities Log.outError(LogFilter.Player, "STORAGE: Can't equip or store initial item {0} for race {1} class {2}, error msg = {3}", itemId, GetRace(), GetClass(), msg); return false; } + public Item StoreNewItem(List pos, uint itemId, bool update, uint randomBonusListId = 0, List allowedLooters = null, ItemContext context = 0, List bonusListIDs = null, bool addToCollection = true) { uint count = 0; foreach (var itemPosCount in pos) count += itemPosCount.count; - Item item = Item.CreateItem(itemId, count, context, this); + Item item = Item.CreateItem(itemId, count, context, this, bonusListIDs == null); if (item != null) { item.SetItemFlag(ItemFieldFlags.NewItem); - item.SetBonuses(bonusListIDs); + if (bonusListIDs != null) + item.SetBonuses(bonusListIDs); item = StoreItem(pos, item, update); @@ -6090,7 +6089,7 @@ namespace Game.Entities InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); if (msg == InventoryResult.Ok) { - Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context, item.BonusListIDs); + Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context); if (ffaItem != null) { //freeforall case, notify only one player of the removal diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index c4b66b931..59408ae90 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -922,7 +922,7 @@ namespace Game.Entities return false; } - public void RewardQuestPackage(uint questPackageId, uint onlyItemId = 0) + public void RewardQuestPackage(uint questPackageId, ItemContext context, uint onlyItemId = 0) { bool hasFilteredQuestPackageReward = false; var questPackageItems = Global.DB2Mgr.GetQuestPackageItems(questPackageId); @@ -939,7 +939,7 @@ namespace Game.Entities List dest = new(); if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemQuantity) == InventoryResult.Ok) { - Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(questPackageItem.ItemID)); + Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(questPackageItem.ItemID), null, context); SendNewItem(item, questPackageItem.ItemQuantity, true, false); } } @@ -959,7 +959,7 @@ namespace Game.Entities List dest = new(); if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, questPackageItem.ItemID, questPackageItem.ItemQuantity) == InventoryResult.Ok) { - Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(questPackageItem.ItemID)); + Item item = StoreNewItem(dest, questPackageItem.ItemID, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(questPackageItem.ItemID), null, context); SendNewItem(item, questPackageItem.ItemQuantity, true, false); } } @@ -1020,7 +1020,7 @@ namespace Game.Entities List dest = new(); if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, quest.RewardItemCount[i]) == InventoryResult.Ok) { - Item item = StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId)); + Item item = StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, ItemContext.QuestReward); SendNewItem(item, quest.RewardItemCount[i], true, false); } else if (quest.IsDFQuest()) @@ -1035,8 +1035,8 @@ namespace Game.Entities { if (quest.IsWorldQuest()) currencyGainSource = CurrencyGainSource.WorldQuestRewardIgnoreCaps; - - currencyGainSource = CurrencyGainSource.QuestRewardIgnoreCaps; + else + currencyGainSource = CurrencyGainSource.QuestRewardIgnoreCaps; } else if (quest.IsDaily()) currencyGainSource = CurrencyGainSource.DailyQuestReward; @@ -1058,7 +1058,7 @@ namespace Game.Entities List dest = new(); if (CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, rewardId, quest.RewardChoiceItemCount[i]) == InventoryResult.Ok) { - Item item = StoreNewItem(dest, rewardId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(rewardId)); + Item item = StoreNewItem(dest, rewardId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(rewardId), null, ItemContext.QuestReward); SendNewItem(item, quest.RewardChoiceItemCount[i], true, false); } } @@ -1068,7 +1068,7 @@ namespace Game.Entities // QuestPackageItem.db2 if (rewardProto != null && quest.PackageID != 0) - RewardQuestPackage(quest.PackageID, rewardId); + RewardQuestPackage(quest.PackageID, ItemContext.QuestReward, rewardId); break; case LootItemType.Currency: if (CliDB.CurrencyTypesStorage.HasRecord(rewardId) && quest.GetRewChoiceItemsCount() != 0) diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index be475c71e..d7e42e286 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -585,7 +585,7 @@ namespace Game.Entities ApplyLevelScaling(); CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); - SetCreateHealth((uint)Math.Max(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, creatureDifficulty.GetHealthScalingExpansion(), m_unitData.ContentTuningID, (Class)cinfo.UnitClass) * creatureDifficulty.HealthModifier * GetHealthMod(cinfo.Rank), 1.0f)); + SetCreateHealth((uint)Math.Max(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, creatureDifficulty.GetHealthScalingExpansion(), m_unitData.ContentTuningID, (Class)cinfo.UnitClass, 0) * creatureDifficulty.HealthModifier * GetHealthMod(cinfo.Rank), 1.0f)); SetCreateMana(stats.BaseMana); SetCreateStat(Stats.Strength, 22); diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 14766a252..69af93351 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -773,7 +773,7 @@ namespace Game.Entities { creature.m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(dungeonEncounter.Id, creature.GetLootId(), LootStorage.Creature, LootType.Corpse, creature, creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax, - (ushort)creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext(), tappers); + (ushort)creature.GetLootMode(), creature.GetMap().GetMapDifficulty(), tappers); } else if (!tappers.Empty()) { @@ -784,7 +784,7 @@ namespace Game.Entities uint lootid = creature.GetLootId(); if (lootid != 0) - loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); + loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), ItemBonusMgr.GetContextForPlayer(creature.GetMap().GetMapDifficulty(), looter)); if (creature.GetLootMode() > 0) loot.GenerateMoneyLoot(creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax); @@ -811,7 +811,7 @@ namespace Game.Entities uint lootid = creature.GetLootId(); if (lootid != 0) - loot.FillLoot(lootid, LootStorage.Creature, tapper, true, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); + loot.FillLoot(lootid, LootStorage.Creature, tapper, true, false, creature.GetLootMode(), ItemBonusMgr.GetContextForPlayer(creature.GetMap().GetMapDifficulty(), tapper)); if (creature.GetLootMode() > 0) loot.GenerateMoneyLoot(creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax); diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 8a0de2f6a..0f45d075e 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -3886,7 +3886,7 @@ namespace Game.Entities } // Expansion and ContentTuningID necessary? Does Player get a ContentTuningID too ? - float armorConstant = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, attackerLevel, -2, 0, attackerClass); + float armorConstant = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, attackerLevel, -2, 0, attackerClass, 0); if ((armor + armorConstant) == 0) return damage; diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 23dbef160..1f69972ba 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -5162,11 +5162,11 @@ namespace Game return false; } - foreach (uint bonusList in vItem.BonusListIDs) + foreach (uint bonusListId in vItem.BonusListIDs) { - if (Global.DB2Mgr.GetItemBonusList(bonusList) == null) + if (ItemBonusMgr.GetItemBonuses(bonusListId).Empty()) { - Log.outError(LogFilter.Sql, "Table `(game_event_)npc_vendor` have Item (Entry: {0}) with invalid bonus {1} for vendor ({2}), ignore", vItem.item, bonusList, vendorentry); + Log.outError(LogFilter.Sql, "Table `(game_event_)npc_vendor` have Item (Entry: {0}) with invalid bonus {1} for vendor ({2}), ignore", vItem.item, bonusListId, vendorentry); return false; } } diff --git a/Source/Game/Handlers/MailHandler.cs b/Source/Game/Handlers/MailHandler.cs index 8e8558c7d..8c6643e91 100644 --- a/Source/Game/Handlers/MailHandler.cs +++ b/Source/Game/Handlers/MailHandler.cs @@ -585,8 +585,8 @@ namespace Game return; } - Item bodyItem = new(); // This is not bag and then can be used new Item. - if (!bodyItem.Create(Global.ObjectMgr.GetGenerator(HighGuid.Item).Generate(), 8383, ItemContext.None, player)) + Item bodyItem = Item.CreateItem(8383, 1, ItemContext.None, player); + if (bodyItem == null) return; // in mail template case we need create new item text diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index 1bb7e28f1..aab1d2785 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -773,7 +773,7 @@ namespace Game _player.SetTitle(CliDB.CharTitlesStorage.LookupByKey(reward.TitleId), false); if (reward.PackageId != 0) - _player.RewardQuestPackage((uint)reward.PackageId); + _player.RewardQuestPackage((uint)reward.PackageId, ItemContext.None); if (reward.SkillLineId != 0 && _player.HasSkill((SkillType)reward.SkillLineId)) _player.UpdateSkillPro((uint)reward.SkillLineId, 1000, reward.SkillPointCount); diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index 273811405..473ac1dcb 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -673,11 +673,7 @@ namespace Game.Loots generatedLoot.context = _itemContext; generatedLoot.count = (byte)Math.Min(count, proto.GetMaxStackSize()); generatedLoot.LootListId = (uint)items.Count; - if (_itemContext != 0) - { - List bonusListIDs = Global.DB2Mgr.GetDefaultItemBonusTree(generatedLoot.itemid, _itemContext); - generatedLoot.BonusListIDs.AddRange(bonusListIDs); - } + generatedLoot.BonusListIDs = ItemBonusMgr.GetBonusListsForItem(generatedLoot.itemid, new(_itemContext)); items.Add(generatedLoot); count -= proto.GetMaxStackSize(); diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index 952fa84f8..bfcc5e492 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -52,7 +52,7 @@ namespace Game.Loots } public static Dictionary GenerateDungeonEncounterPersonalLoot(uint dungeonEncounterId, uint lootId, LootStore store, - LootType type, WorldObject lootOwner, uint minMoney, uint maxMoney, ushort lootMode, ItemContext context, List tappers) + LootType type, WorldObject lootOwner, uint minMoney, uint maxMoney, ushort lootMode, MapDifficultyRecord mapDifficulty, List tappers) { Dictionary tempLoot = new(); @@ -62,7 +62,7 @@ namespace Game.Loots continue; Loot loot = new(lootOwner.GetMap(), lootOwner.GetGUID(), type, null); - loot.SetItemContext(context); + loot.SetItemContext(ItemBonusMgr.GetContextForPlayer(mapDifficulty, tapper)); loot.SetDungeonEncounterId(dungeonEncounterId); loot.GenerateMoneyLoot(minMoney, maxMoney); diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 24c3d98dd..04bb745d5 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -3228,19 +3228,6 @@ namespace Game.Maps return Global.DB2Mgr.GetMapDifficultyData(GetId(), GetDifficultyID()); } - public ItemContext GetDifficultyLootItemContext() - { - MapDifficultyRecord mapDifficulty = GetMapDifficulty(); - if (mapDifficulty != null && mapDifficulty.ItemContext != 0) - return (ItemContext)mapDifficulty.ItemContext; - - DifficultyRecord difficulty = CliDB.DifficultyStorage.LookupByKey(GetDifficultyID()); - if (difficulty != null) - return (ItemContext)difficulty.ItemContext; - - return ItemContext.None; - } - public uint GetId() { return i_mapRecord.Id; diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 75375a6db..d435e46ed 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -4306,7 +4306,7 @@ namespace Game.Spells expansion = contentTuning.ExpansionID; uint level = caster != null && caster.IsUnit() ? caster.ToUnit().GetLevel() : 1; - tempValue = Global.DB2Mgr.EvaluateExpectedStat(stat, level, expansion, 0, Class.None) * BasePoints / 100.0f; + tempValue = Global.DB2Mgr.EvaluateExpectedStat(stat, level, expansion, 0, Class.None, 0) * BasePoints / 100.0f; } return (int)Math.Round(tempValue); diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index b59a41219..9973f0801 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -580,6 +580,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Enchant Spells Proc datas..."); Global.SpellMgr.LoadSpellEnchantProcData(); + Log.outInfo(LogFilter.ServerLoading, "Loading item bonus data..."); + ItemBonusMgr.Load(); + Log.outInfo(LogFilter.ServerLoading, "Loading Random item bonus list definitions..."); ItemEnchantmentManager.LoadItemRandomBonusListTemplates(); diff --git a/Source/Scripts/Spells/Monk.cs b/Source/Scripts/Spells/Monk.cs index 7edfa4807..4b4e713e2 100644 --- a/Source/Scripts/Spells/Monk.cs +++ b/Source/Scripts/Spells/Monk.cs @@ -278,7 +278,7 @@ namespace Scripts.Spells.Monk Unit target = GetTarget(); float agility = target.GetStat(Stats.Agility); float baseAmount = MathFunctions.CalculatePct(agility, effect.GetAmount()); - float K = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, target.GetLevel(), -2, 0, target.GetClass()); + float K = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, target.GetLevel(), -2, 0, target.GetClass(), 0); float newAmount = (baseAmount / (baseAmount + K)); newAmount *= multiplier;