/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "ItemBonusMgr.h"
#include "ConditionMgr.h"
#include "DB2Stores.h"
#include "MapUtils.h"
#include "ObjectMgr.h"
#include "Player.h"
namespace
{
struct ItemLevelSelectorQualityEntryComparator
{
bool operator()(ItemLevelSelectorQualityEntry const* left, ItemLevelSelectorQualityEntry const* right) const { return left->Quality < right->Quality; }
bool operator()(ItemLevelSelectorQualityEntry const* left, ItemQualities quality) const { return left->Quality < quality; }
};
using ItemLevelSelectorQualities = std::set;
std::unordered_multimap _azeriteUnlockMappings;
std::unordered_multimap _challengeModeItemBonusOverrides;
std::unordered_map> _itemBonusLists;
std::unordered_multimap _itemBonusListGroupEntries;
std::unordered_map _itemLevelDeltaToBonusListContainer;
std::unordered_map _itemLevelQualitySelectorQualities;
std::unordered_map> _itemBonusTrees;
std::unordered_multimap _itemToBonusTree;
}
namespace ItemBonusMgr
{
void Load()
{
for (AzeriteUnlockMappingEntry const* azeriteUnlockMapping : sAzeriteUnlockMappingStore)
_azeriteUnlockMappings.emplace(azeriteUnlockMapping->AzeriteUnlockMappingSetID, azeriteUnlockMapping);
for (ChallengeModeItemBonusOverrideEntry const* challengeModeItemBonusOverride : sChallengeModeItemBonusOverrideStore)
_challengeModeItemBonusOverrides.emplace(challengeModeItemBonusOverride->SrcItemBonusTreeID, challengeModeItemBonusOverride);
for (ItemBonusEntry const* bonus : sItemBonusStore)
_itemBonusLists[bonus->ParentItemBonusListID].push_back(bonus);
for (ItemBonusListGroupEntryEntry const* bonusListGroupEntry : sItemBonusListGroupEntryStore)
_itemBonusListGroupEntries.emplace(bonusListGroupEntry->ItemBonusListGroupID, bonusListGroupEntry);
for (ItemBonusListLevelDeltaEntry const* itemBonusListLevelDelta : sItemBonusListLevelDeltaStore)
_itemLevelDeltaToBonusListContainer[itemBonusListLevelDelta->ItemLevelDelta] = itemBonusListLevelDelta->ID;
for (ItemLevelSelectorQualityEntry const* itemLevelSelectorQuality : sItemLevelSelectorQualityStore)
_itemLevelQualitySelectorQualities[itemLevelSelectorQuality->ParentILSQualitySetID].insert(itemLevelSelectorQuality);
for (ItemBonusTreeNodeEntry const* bonusTreeNode : sItemBonusTreeNodeStore)
_itemBonusTrees[bonusTreeNode->ParentItemBonusTreeID].insert(bonusTreeNode);
for (ItemXBonusTreeEntry const* itemBonusTreeAssignment : sItemXBonusTreeStore)
_itemToBonusTree.insert({ itemBonusTreeAssignment->ItemID, itemBonusTreeAssignment->ItemBonusTreeID });
}
ItemContext GetContextForPlayer(MapDifficultyEntry const* mapDifficulty, Player const* player)
{
if (!mapDifficulty)
return ItemContext::NONE;
auto evalContext = [](ItemContext currentContext, ItemContext newContext)
{
if (newContext == ItemContext::NONE)
newContext = currentContext;
else if (newContext == ItemContext::Force_to_NONE)
newContext = ItemContext::NONE;
return newContext;
};
ItemContext context = ItemContext::NONE;
if (DifficultyEntry const* difficulty = sDifficultyStore.LookupEntry(mapDifficulty->DifficultyID))
context = evalContext(context, ItemContext(difficulty->ItemContext));
context = evalContext(context, ItemContext(mapDifficulty->ItemContext));
if (mapDifficulty->ItemContextPickerID)
{
uint32 contentTuningId = sDB2Manager.GetRedirectedContentTuningId(mapDifficulty->ContentTuningID, player->m_playerData->CtrOptions->ConditionalFlags);
ItemContextPickerEntryEntry const* selectedPickerEntry = nullptr;
for (ItemContextPickerEntryEntry const* itemContextPickerEntry : sItemContextPickerEntryStore)
{
if (itemContextPickerEntry->ItemContextPickerID != uint32(mapDifficulty->ItemContextPickerID))
continue;
if (itemContextPickerEntry->PVal <= 0)
continue;
bool meetsPlayerCondition = false;
if (player)
meetsPlayerCondition = ConditionMgr::IsPlayerMeetingCondition(player, itemContextPickerEntry->PlayerConditionID);
if (itemContextPickerEntry->Flags & 0x1)
meetsPlayerCondition = !meetsPlayerCondition;
if (!meetsPlayerCondition)
continue;
if (itemContextPickerEntry->LabelID && !sDB2Manager.HasContentTuningLabel(contentTuningId, itemContextPickerEntry->LabelID))
continue;
if (!selectedPickerEntry || selectedPickerEntry->OrderIndex < itemContextPickerEntry->OrderIndex)
selectedPickerEntry = itemContextPickerEntry;
}
if (selectedPickerEntry)
context = evalContext(context, ItemContext(selectedPickerEntry->ItemCreationContext));
}
return context;
}
std::span GetItemBonuses(uint32 bonusListId)
{
if (std::vector* itemBonusEntries = Trinity::Containers::MapGetValuePtr(_itemBonusLists, bonusListId))
return std::span(*itemBonusEntries);
return {};
}
uint32 GetItemBonusListForItemLevelDelta(int16 delta)
{
if (uint32 const* itemBonusListId = Trinity::Containers::MapGetValuePtr(_itemLevelDeltaToBonusListContainer, delta))
return *itemBonusListId;
return 0;
}
bool CanApplyBonusTreeToItem(ItemTemplate const* itemTemplate, uint32 itemBonusTreeId, ItemBonusGenerationParams const& params)
{
if (ItemBonusTreeEntry const* bonusTree = sItemBonusTreeStore.LookupEntry(itemBonusTreeId))
{
if (bonusTree->InventoryTypeSlotMask)
if (!(1 << itemTemplate->GetInventoryType() & bonusTree->InventoryTypeSlotMask))
return false;
if (bonusTree->Flags & 0x8 && !itemTemplate->HasFlag(ITEM_FLAG2_CASTER_WEAPON))
return false;
if (bonusTree->Flags & 0x10 && itemTemplate->HasFlag(ITEM_FLAG2_CASTER_WEAPON))
return false;
if (bonusTree->Flags & 0x20 && !itemTemplate->HasFlag(ITEM_FLAG4_CC_TRINKET))
return false;
if (bonusTree->Flags & 0x40 && itemTemplate->HasFlag(ITEM_FLAG4_CC_TRINKET))
return false;
if (bonusTree->Flags & 0x4)
return true;
}
if (std::set* bonusTreeNodes = Trinity::Containers::MapGetValuePtr(_itemBonusTrees, itemBonusTreeId))
{
bool anyNodeMatched = false;
for (ItemBonusTreeNodeEntry const* bonusTreeNode : *bonusTreeNodes)
{
if (bonusTreeNode->MinMythicPlusLevel > 0)
continue;
ItemContext nodeContext = ItemContext(bonusTreeNode->ItemContext);
if (nodeContext == ItemContext::NONE || nodeContext == params.Context)
{
if (anyNodeMatched)
return false;
anyNodeMatched = true;
}
}
}
return true;
}
uint32 GetBonusTreeIdOverride(uint32 itemBonusTreeId, ItemBonusGenerationParams const& params)
{
std::vector passedTimeEvents; // sorted by date TODO: configure globally
if (!passedTimeEvents.empty())
{
int32 selectedLevel = -1;
std::ptrdiff_t selectedMilestoneSeason = -1;
ChallengeModeItemBonusOverrideEntry const* selectedItemBonusOverride = nullptr;
for (auto& [_, itemBonusOverride] : Trinity::Containers::MapEqualRange(_challengeModeItemBonusOverrides, itemBonusTreeId))
{
if (params.MythicPlusKeystoneLevel && itemBonusOverride->Value > *params.MythicPlusKeystoneLevel)
continue;
if (params.PvpTier && itemBonusOverride->Value > *params.PvpTier)
continue;
if (itemBonusOverride->RequiredTimeEventPassed)
{
auto itr = std::ranges::find(passedTimeEvents, itemBonusOverride->RequiredTimeEventPassed);
if (itr == passedTimeEvents.end())
continue; // season not started yet
std::ptrdiff_t overrideMilestoneSeason = std::ranges::distance(passedTimeEvents.begin(), itr);
if (selectedMilestoneSeason > overrideMilestoneSeason)
continue; // older season that what was selected
if (selectedMilestoneSeason == overrideMilestoneSeason)
if (selectedLevel > itemBonusOverride->Value)
continue; // lower level in current season than what was already found
selectedMilestoneSeason = overrideMilestoneSeason;
}
else if (selectedLevel > itemBonusOverride->Value)
continue;
selectedLevel = itemBonusOverride->Value;
selectedItemBonusOverride = itemBonusOverride;
}
if (selectedItemBonusOverride && selectedItemBonusOverride->DstItemBonusTreeID)
itemBonusTreeId = selectedItemBonusOverride->DstItemBonusTreeID;
}
return itemBonusTreeId;
}
void ApplyBonusTreeHelper(ItemTemplate const* itemTemplate, uint32 itemBonusTreeId, ItemBonusGenerationParams const& params, int32 sequenceLevel, uint32* itemLevelSelectorId, std::vector* bonusListIDs)
{
uint32 originalItemBonusTreeId = itemBonusTreeId;
// override bonus tree with season specific values
itemBonusTreeId = GetBonusTreeIdOverride(itemBonusTreeId, params);
if (!CanApplyBonusTreeToItem(itemTemplate, itemBonusTreeId, params))
return;
auto treeItr = _itemBonusTrees.find(itemBonusTreeId);
if (treeItr == _itemBonusTrees.end())
return;
for (ItemBonusTreeNodeEntry const* bonusTreeNode : treeItr->second)
{
ItemContext nodeContext = ItemContext(bonusTreeNode->ItemContext);
ItemContext requiredContext = nodeContext != ItemContext::Force_to_NONE ? nodeContext : ItemContext::NONE;
if (nodeContext != ItemContext::NONE && params.Context != requiredContext)
continue;
if (params.MythicPlusKeystoneLevel)
{
if (bonusTreeNode->MinMythicPlusLevel && params.MythicPlusKeystoneLevel < bonusTreeNode->MinMythicPlusLevel)
continue;
if (bonusTreeNode->MaxMythicPlusLevel && params.MythicPlusKeystoneLevel > bonusTreeNode->MaxMythicPlusLevel)
continue;
}
if (bonusTreeNode->ChildItemBonusTreeID)
ApplyBonusTreeHelper(itemTemplate, bonusTreeNode->ChildItemBonusTreeID, params, sequenceLevel, itemLevelSelectorId, bonusListIDs);
else if (bonusTreeNode->ChildItemBonusListID)
bonusListIDs->push_back(bonusTreeNode->ChildItemBonusListID);
else if (bonusTreeNode->ChildItemLevelSelectorID)
*itemLevelSelectorId = bonusTreeNode->ChildItemLevelSelectorID;
else if (bonusTreeNode->ChildItemBonusListGroupID)
{
int32 resolvedSequenceLevel = sequenceLevel;
switch (originalItemBonusTreeId)
{
case 4001:
resolvedSequenceLevel = 1;
break;
case 4079:
if (params.MythicPlusKeystoneLevel)
{
switch (bonusTreeNode->IblGroupPointsModSetID)
{
case 2909: // MythicPlus_End_of_Run levels 2-8
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(62951, *params.MythicPlusKeystoneLevel);
break;
case 2910: // MythicPlus_End_of_Run levels 9-16
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(62952, *params.MythicPlusKeystoneLevel);
break;
case 2911: // MythicPlus_End_of_Run levels 17-20
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(62954, *params.MythicPlusKeystoneLevel);
break;
case 3007: // MythicPlus_Jackpot (weekly reward) levels 2-7
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(64388, *params.MythicPlusKeystoneLevel);
break;
case 3008: // MythicPlus_Jackpot (weekly reward) levels 8-15
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(64389, *params.MythicPlusKeystoneLevel);
break;
case 3009: // MythicPlus_Jackpot (weekly reward) levels 16-20
resolvedSequenceLevel = sDB2Manager.GetCurveValueAt(64395, *params.MythicPlusKeystoneLevel);
break;
default:
break;
}
}
break;
case 4125:
resolvedSequenceLevel = 2;
break;
case 4126:
resolvedSequenceLevel = 3;
break;
case 4127:
resolvedSequenceLevel = 4;
break;
case 4128:
switch (params.Context)
{
case ItemContext::Raid_Normal:
case ItemContext::Raid_Raid_Finder:
case ItemContext::Raid_Heroic:
resolvedSequenceLevel = 2;
break;
case ItemContext::Raid_Mythic:
resolvedSequenceLevel = 6;
break;
default:
break;
}
break;
case 4140:
switch (params.Context)
{
case ItemContext::Dungeon_Normal:
resolvedSequenceLevel = 2;
break;
case ItemContext::Dungeon_Mythic:
resolvedSequenceLevel = 4;
break;
default:
break;
}
break;
default:
break;
}
for (auto const& [_, bonusListGroupEntry] : Trinity::Containers::MapEqualRange(_itemBonusListGroupEntries, bonusTreeNode->ChildItemBonusListGroupID))
{
if ((resolvedSequenceLevel > 0 || bonusListGroupEntry->SequenceValue <= 0) && resolvedSequenceLevel != bonusListGroupEntry->SequenceValue)
continue;
*itemLevelSelectorId = bonusListGroupEntry->ItemLevelSelectorID;
bonusListIDs->push_back(bonusListGroupEntry->ItemBonusListID);
break;
}
}
}
}
int32 GetAzeriteUnlockBonusList(uint16 azeriteUnlockMappingSetId, uint16 minItemLevel, InventoryType inventoryType)
{
AzeriteUnlockMappingEntry const* selectedAzeriteUnlockMapping = nullptr;
for (auto [_, azeriteUnlockMapping] : Trinity::Containers::MapEqualRange(_azeriteUnlockMappings, azeriteUnlockMappingSetId))
{
if (minItemLevel < azeriteUnlockMapping->ItemLevel)
continue;
if (selectedAzeriteUnlockMapping && selectedAzeriteUnlockMapping->ItemLevel > azeriteUnlockMapping->ItemLevel)
continue;
selectedAzeriteUnlockMapping = azeriteUnlockMapping;
}
if (selectedAzeriteUnlockMapping)
{
switch (inventoryType)
{
case INVTYPE_HEAD:
return selectedAzeriteUnlockMapping->ItemBonusListHead;
case INVTYPE_SHOULDERS:
return selectedAzeriteUnlockMapping->ItemBonusListShoulders;
case INVTYPE_CHEST:
case INVTYPE_ROBE:
return selectedAzeriteUnlockMapping->ItemBonusListChest;
default:
break;
}
}
return 0;
}
std::vector GetBonusListsForItem(uint32 itemId, ItemBonusGenerationParams const& params)
{
std::vector bonusListIDs;
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId);
if (!itemTemplate)
return bonusListIDs;
uint32 itemLevelSelectorId = 0;
for (auto [_, itemBonusTreeId] : Trinity::Containers::MapEqualRange(_itemToBonusTree, itemId))
ApplyBonusTreeHelper(itemTemplate, itemBonusTreeId, params, 0, &itemLevelSelectorId, &bonusListIDs);
if (ItemLevelSelectorEntry const* selector = sItemLevelSelectorStore.LookupEntry(itemLevelSelectorId))
{
int16 delta = int16(selector->MinItemLevel) - int16(itemTemplate->GetBaseItemLevel());
if (uint32 bonus = GetItemBonusListForItemLevelDelta(delta))
bonusListIDs.push_back(bonus);
if (ItemLevelSelectorQualitySetEntry const* selectorQualitySet = sItemLevelSelectorQualitySetStore.LookupEntry(selector->ItemLevelSelectorQualitySetID))
{
auto itemSelectorQualities = _itemLevelQualitySelectorQualities.find(selector->ItemLevelSelectorQualitySetID);
if (itemSelectorQualities != _itemLevelQualitySelectorQualities.end())
{
ItemQualities quality = ITEM_QUALITY_UNCOMMON;
if (selector->MinItemLevel >= selectorQualitySet->IlvlEpic)
quality = ITEM_QUALITY_EPIC;
else if (selector->MinItemLevel >= selectorQualitySet->IlvlRare)
quality = ITEM_QUALITY_RARE;
auto itemSelectorQuality = std::lower_bound(itemSelectorQualities->second.begin(), itemSelectorQualities->second.end(),
quality, ItemLevelSelectorQualityEntryComparator{});
if (itemSelectorQuality != itemSelectorQualities->second.end())
bonusListIDs.push_back((*itemSelectorQuality)->QualityItemBonusListID);
}
}
if (int32 azeriteUnlockBonusListId = GetAzeriteUnlockBonusList(selector->AzeriteUnlockMappingSet, selector->MinItemLevel, itemTemplate->GetInventoryType()))
bonusListIDs.push_back(azeriteUnlockBonusListId);
}
return bonusListIDs;
}
template
void VisitItemBonusTree(uint32 itemBonusTreeId, Visitor visitor)
{
auto treeItr = _itemBonusTrees.find(itemBonusTreeId);
if (treeItr == _itemBonusTrees.end())
return;
for (ItemBonusTreeNodeEntry const* bonusTreeNode : treeItr->second)
{
visitor(bonusTreeNode);
if (bonusTreeNode->ChildItemBonusTreeID)
VisitItemBonusTree(bonusTreeNode->ChildItemBonusTreeID, visitor);
}
}
std::vector GetAllBonusListsForTree(uint32 itemBonusTreeId)
{
std::vector bonusListIDs;
VisitItemBonusTree(itemBonusTreeId, [&bonusListIDs](ItemBonusTreeNodeEntry const* bonusTreeNode)
{
if (bonusTreeNode->ChildItemBonusListID)
bonusListIDs.push_back(bonusTreeNode->ChildItemBonusListID);
});
return bonusListIDs;
}
}