/*
* 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 "Item.h"
#include "ArtifactPackets.h"
#include "AzeriteEmpoweredItem.h"
#include "AzeriteItem.h"
#include "Bag.h"
#include "CollectionMgr.h"
#include "Common.h"
#include "ConditionMgr.h"
#include "DB2Stores.h"
#include "DatabaseEnv.h"
#include "GameTables.h"
#include "GameTime.h"
#include "ItemBonusMgr.h"
#include "ItemEnchantmentMgr.h"
#include "ItemPackets.h"
#include "Log.h"
#include "Loot.h"
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "Map.h"
#include "MapUtils.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "StringConvert.h"
#include "TradeData.h"
#include "UpdateData.h"
#include "World.h"
#include "WorldSession.h"
#include
Item* NewItemOrBag(ItemTemplate const* proto)
{
if (proto->GetInventoryType() == INVTYPE_BAG)
return new Bag();
if (sDB2Manager.IsAzeriteItem(proto->GetId()))
return new AzeriteItem();
if (sDB2Manager.GetAzeriteEmpoweredItem(proto->GetId()))
return new AzeriteEmpoweredItem();
return new Item();
}
struct ItemSetEffect
{
uint32 ItemSetID;
std::unordered_set- EquippedItems;
std::unordered_set SetBonuses;
};
void AddItemsSetItem(Player* player, Item const* item)
{
ItemTemplate const* proto = item->GetTemplate();
uint32 setid = proto->GetItemSet();
ItemSetEntry const* set = sItemSetStore.LookupEntry(setid);
if (!set)
{
TC_LOG_ERROR("sql.sql", "Item set {} for item (id {}) not found, mods not applied.", setid, proto->GetId());
return;
}
if (set->RequiredSkill && player->GetSkillValue(set->RequiredSkill) < set->RequiredSkillRank)
return;
if (set->SetFlags & ITEM_SET_FLAG_LEGACY_INACTIVE)
return;
// Check player level for heirlooms
if (sDB2Manager.GetHeirloomByItemId(item->GetEntry()))
{
if (item->GetBonus()->PlayerLevelToItemLevelCurveId)
{
uint32 maxLevel = sDB2Manager.GetCurveXAxisRange(item->GetBonus()->PlayerLevelToItemLevelCurveId).second;
if (Optional contentTuning = sDB2Manager.GetContentTuningData(item->GetBonus()->ContentTuningId, player->m_playerData->CtrOptions->ConditionalFlags, true))
maxLevel = std::min(maxLevel, contentTuning->MaxLevel);
if (player->GetLevel() > maxLevel)
return;
}
}
ItemSetEffect* eff = nullptr;
for (size_t x = 0; x < player->ItemSetEff.size(); ++x)
{
if (player->ItemSetEff[x] && player->ItemSetEff[x]->ItemSetID == setid)
{
eff = player->ItemSetEff[x];
break;
}
}
if (!eff)
{
eff = new ItemSetEffect();
eff->ItemSetID = setid;
size_t x = 0;
for (; x < player->ItemSetEff.size(); ++x)
if (!player->ItemSetEff[x])
break;
if (x < player->ItemSetEff.size())
player->ItemSetEff[x] = eff;
else
player->ItemSetEff.push_back(eff);
}
eff->EquippedItems.insert(item);
if (std::vector const* itemSetSpells = sDB2Manager.GetItemSetSpells(setid))
{
for (ItemSetSpellEntry const* itemSetSpell : *itemSetSpells)
{
//not enough for spell
if (itemSetSpell->Threshold > eff->EquippedItems.size())
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itemSetSpell->SpellID, DIFFICULTY_NONE);
if (!spellInfo)
{
TC_LOG_ERROR("entities.player.items", "WORLD: unknown spell id {} in items set {} effects", itemSetSpell->SpellID, setid);
continue;
}
if (!eff->SetBonuses.insert(itemSetSpell).second)
continue;
// spell cast only if fit form requirement, in other case will cast at form change
if (itemSetSpell->ChrSpecID && ChrSpecialization(itemSetSpell->ChrSpecID) != player->GetPrimarySpecialization())
continue;
if (itemSetSpell->TraitSubTreeID && int32(itemSetSpell->TraitSubTreeID) != player->m_playerData->CurrentCombatTraitConfigSubTreeID)
continue;
player->ApplyEquipSpell(spellInfo, nullptr, true);
}
}
}
void RemoveItemsSetItem(Player* player, Item const* item)
{
uint32 setid = item->GetTemplate()->GetItemSet();
ItemSetEntry const* set = sItemSetStore.LookupEntry(setid);
if (!set)
{
TC_LOG_ERROR("sql.sql", "Item set #{} for item #{} not found, mods not removed.", setid, item->GetEntry());
return;
}
ItemSetEffect* eff = nullptr;
size_t setindex = 0;
for (; setindex < player->ItemSetEff.size(); setindex++)
{
if (player->ItemSetEff[setindex] && player->ItemSetEff[setindex]->ItemSetID == setid)
{
eff = player->ItemSetEff[setindex];
break;
}
}
// can be in case now enough skill requirement for set appling but set has been appliend when skill requirement not enough
if (!eff)
return;
eff->EquippedItems.erase(item);
if (std::vector const* itemSetSpells = sDB2Manager.GetItemSetSpells(setid))
{
for (ItemSetSpellEntry const* itemSetSpell : *itemSetSpells)
{
// enough for spell
if (itemSetSpell->Threshold <= eff->EquippedItems.size())
continue;
if (!eff->SetBonuses.erase(itemSetSpell))
continue;
player->ApplyEquipSpell(sSpellMgr->AssertSpellInfo(itemSetSpell->SpellID, DIFFICULTY_NONE), nullptr, false);
}
}
if (eff->EquippedItems.empty()) //all items of a set were removed
{
ASSERT(eff == player->ItemSetEff[setindex]);
delete eff;
player->ItemSetEff[setindex] = nullptr;
}
}
void UpdateItemSetAuras(Player* player, bool formChange)
{
// item set bonuses not dependent from item broken state
for (ItemSetEffect* eff : player->ItemSetEff)
{
if (!eff)
continue;
for (ItemSetSpellEntry const* itemSetSpell : eff->SetBonuses)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itemSetSpell->SpellID, DIFFICULTY_NONE);
if ((itemSetSpell->ChrSpecID && ChrSpecialization(itemSetSpell->ChrSpecID) != player->GetPrimarySpecialization())
|| (itemSetSpell->TraitSubTreeID && int32(itemSetSpell->TraitSubTreeID) != player->m_playerData->CurrentCombatTraitConfigSubTreeID))
player->ApplyEquipSpell(spellInfo, nullptr, false, false); // item set aura is not for current spec
else
{
player->ApplyEquipSpell(spellInfo, nullptr, false, formChange); // remove spells that not fit to form - removal is skipped if shapeshift condition is satisfied
player->ApplyEquipSpell(spellInfo, nullptr, true, formChange); // add spells that fit form but not active
}
}
}
}
void DeleteItemSetEffects(ItemSetEffect* itemSetEffect)
{
delete itemSetEffect;
}
bool ItemCanGoIntoBag(ItemTemplate const* pProto, ItemTemplate const* pBagProto)
{
if (!pProto || !pBagProto)
return false;
switch (pBagProto->GetClass())
{
case ITEM_CLASS_CONTAINER:
switch (pBagProto->GetSubClass())
{
case ITEM_SUBCLASS_CONTAINER:
return true;
case ITEM_SUBCLASS_SOUL_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_SOUL_SHARDS))
return false;
return true;
case ITEM_SUBCLASS_HERB_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_HERBS))
return false;
return true;
case ITEM_SUBCLASS_ENCHANTING_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_ENCHANTING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_MINING_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_MINING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_ENGINEERING_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_ENGINEERING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_GEM_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_GEMS))
return false;
return true;
case ITEM_SUBCLASS_LEATHERWORKING_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_LEATHERWORKING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_INSCRIPTION_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_INSCRIPTION_SUPP))
return false;
return true;
case ITEM_SUBCLASS_TACKLE_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_FISHING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_COOKING_CONTAINER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_COOKING_SUPP))
return false;
return true;
case ITEM_SUBCLASS_REAGENT_CONTAINER:
return pProto->IsCraftingReagent();
default:
return false;
}
case ITEM_CLASS_QUIVER:
switch (pBagProto->GetSubClass())
{
case ITEM_SUBCLASS_QUIVER:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_ARROWS))
return false;
return true;
case ITEM_SUBCLASS_AMMO_POUCH:
if (!(pProto->GetBagFamily() & BAG_FAMILY_MASK_BULLETS))
return false;
return true;
default:
return false;
}
}
return false;
}
ItemModifier const AppearanceModifierSlotBySpec[MAX_SPECIALIZATIONS] =
{
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_1,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_2,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_3,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_4,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_5
};
ItemModifier const IllusionModifierSlotBySpec[MAX_SPECIALIZATIONS] =
{
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_1,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_2,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_3,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_4,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_5
};
ItemModifier const SecondaryAppearanceModifierSlotBySpec[MAX_SPECIALIZATIONS] =
{
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_1,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_2,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_3,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_4,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_5
};
void ItemAdditionalLoadInfo::Init(std::unordered_map* loadInfo,
PreparedQueryResult artifactResult, PreparedQueryResult azeriteItemResult,
PreparedQueryResult azeriteItemMilestonePowersResult, PreparedQueryResult azeriteItemUnlockedEssencesResult,
PreparedQueryResult azeriteEmpoweredItemResult)
{
// 0 1 2 3 4 5
// SELECT a.itemGuid, a.xp, a.artifactAppearanceId, a.artifactTierId, ap.artifactPowerId, ap.purchasedRank FROM item_instance_artifact_powers ap LEFT JOIN item_instance_artifact a ON ap.itemGuid = a.itemGuid ...
if (artifactResult)
{
do
{
Field* fields = artifactResult->Fetch();
ItemAdditionalLoadInfo& info = (*loadInfo)[fields[0].GetUInt64()];
if (!info.Artifact)
info.Artifact.emplace();
info.Artifact->Xp = fields[1].GetUInt64();
info.Artifact->ArtifactAppearanceId = fields[2].GetUInt32();
info.Artifact->ArtifactTierId = fields[3].GetUInt32();
ArtifactPowerData artifactPowerData;
artifactPowerData.ArtifactPowerId = fields[4].GetUInt32();
artifactPowerData.PurchasedRank = fields[5].GetUInt8();
if (ArtifactPowerEntry const* artifactPower = sArtifactPowerStore.LookupEntry(artifactPowerData.ArtifactPowerId))
{
uint32 maxRank = artifactPower->MaxPurchasableRank;
// allow ARTIFACT_POWER_FLAG_FINAL to overflow maxrank here - needs to be handled in Item::CheckArtifactUnlock (will refund artifact power)
if (artifactPower->Flags & ARTIFACT_POWER_FLAG_MAX_RANK_WITH_TIER && uint32(artifactPower->Tier) < info.Artifact->ArtifactTierId)
maxRank += info.Artifact->ArtifactTierId - artifactPower->Tier;
if (artifactPowerData.PurchasedRank > maxRank)
artifactPowerData.PurchasedRank = maxRank;
artifactPowerData.CurrentRankWithBonus = (artifactPower->Flags & ARTIFACT_POWER_FLAG_FIRST) == ARTIFACT_POWER_FLAG_FIRST ? 1 : 0;
info.Artifact->ArtifactPowers.push_back(artifactPowerData);
}
} while (artifactResult->NextRow());
}
// 0 1 2 3
// SELECT iz.itemGuid, iz.xp, iz.level, iz.knowledgeLevel,
// 4 5 6 7 8
// iz.selectedAzeriteEssences1specId, iz.selectedAzeriteEssences1azeriteEssenceId1, iz.selectedAzeriteEssences1azeriteEssenceId2, iz.selectedAzeriteEssences1azeriteEssenceId3, iz.selectedAzeriteEssences1azeriteEssenceId4,
// 9 10 11 12 13
// iz.selectedAzeriteEssences2specId, iz.selectedAzeriteEssences2azeriteEssenceId1, iz.selectedAzeriteEssences2azeriteEssenceId2, iz.selectedAzeriteEssences2azeriteEssenceId3, iz.selectedAzeriteEssences2azeriteEssenceId4,
// 14 15 16 17 18
// iz.selectedAzeriteEssences3specId, iz.selectedAzeriteEssences3azeriteEssenceId1, iz.selectedAzeriteEssences3azeriteEssenceId2, iz.selectedAzeriteEssences3azeriteEssenceId3, iz.selectedAzeriteEssences3azeriteEssenceId4,
// 19 20 21 22 23
// iz.selectedAzeriteEssences4specId, iz.selectedAzeriteEssences4azeriteEssenceId1, iz.selectedAzeriteEssences4azeriteEssenceId2, iz.selectedAzeriteEssences4azeriteEssenceId3, iz.selectedAzeriteEssences4azeriteEssenceId4
// FROM item_instance_azerite iz INNER JOIN ...
if (azeriteItemResult)
{
do
{
Field* fields = azeriteItemResult->Fetch();
ItemAdditionalLoadInfo& info = (*loadInfo)[fields[0].GetUInt64()];
if (!info.AzeriteItem)
info.AzeriteItem.emplace();
info.AzeriteItem->Xp = fields[1].GetUInt64();
info.AzeriteItem->Level = fields[2].GetUInt32();
info.AzeriteItem->KnowledgeLevel = fields[3].GetUInt32();
for (std::size_t i = 0; i < info.AzeriteItem->SelectedAzeriteEssences.size(); ++i)
{
uint32 specializationId = fields[4 + i * 4].GetUInt32();
if (!sChrSpecializationStore.LookupEntry(specializationId))
continue;
info.AzeriteItem->SelectedAzeriteEssences[i].SpecializationId = specializationId;
for (std::size_t j = 0; j < MAX_AZERITE_ESSENCE_SLOT; ++j)
{
AzeriteEssenceEntry const* azeriteEssence = sAzeriteEssenceStore.LookupEntry(fields[5 + i * 5 + j].GetUInt32());
if (!azeriteEssence || !sDB2Manager.IsSpecSetMember(azeriteEssence->SpecSetID, specializationId))
continue;
info.AzeriteItem->SelectedAzeriteEssences[i].AzeriteEssenceId[j] = azeriteEssence->ID;
}
}
} while (azeriteItemResult->NextRow());
}
// 0 1
// SELECT iamp.itemGuid, iamp.azeriteItemMilestonePowerId FROM item_instance_azerite_milestone_power iamp INNER JOIN ...
if (azeriteItemMilestonePowersResult)
{
do
{
Field* fields = azeriteItemMilestonePowersResult->Fetch();
ItemAdditionalLoadInfo& info = (*loadInfo)[fields[0].GetUInt64()];
if (!info.AzeriteItem)
info.AzeriteItem.emplace();
info.AzeriteItem->AzeriteItemMilestonePowers.push_back(fields[1].GetUInt32());
}
while (azeriteItemMilestonePowersResult->NextRow());
}
// 0 1 2
// SELECT iaue.itemGuid, iaue.azeriteEssenceId, iaue.`rank` FROM item_instance_azerite_unlocked_essence iaue INNER JOIN ...
if (azeriteItemUnlockedEssencesResult)
{
do
{
Field* fields = azeriteItemUnlockedEssencesResult->Fetch();
if (AzeriteEssencePowerEntry const* azeriteEssencePower = sDB2Manager.GetAzeriteEssencePower(fields[1].GetUInt32(), fields[2].GetUInt32()))
{
ItemAdditionalLoadInfo& info = (*loadInfo)[fields[0].GetUInt64()];
if (!info.AzeriteItem)
info.AzeriteItem.emplace();
info.AzeriteItem->UnlockedAzeriteEssences.push_back(azeriteEssencePower);
}
}
while (azeriteItemUnlockedEssencesResult->NextRow());
}
// 0 1 2 3 4 5
// SELECT iae.itemGuid, iae.azeritePowerId1, iae.azeritePowerId2, iae.azeritePowerId3, iae.azeritePowerId4, iae.azeritePowerId5 FROM item_instance_azerite_empowered iae INNER JOIN ...
if (azeriteEmpoweredItemResult)
{
do
{
Field* fields = azeriteEmpoweredItemResult->Fetch();
ItemAdditionalLoadInfo& info = (*loadInfo)[fields[0].GetUInt64()];
if (!info.AzeriteEmpoweredItem)
info.AzeriteEmpoweredItem.emplace();
for (uint32 i = 0; i < MAX_AZERITE_EMPOWERED_TIER; ++i)
if (sAzeritePowerStore.LookupEntry(fields[1 + i].GetInt32()))
info.AzeriteEmpoweredItem->SelectedAzeritePowers[i] = fields[1 + i].GetInt32();
} while (azeriteEmpoweredItemResult->NextRow());
}
}
Item::Item()
{
m_objectType |= TYPEMASK_ITEM;
m_objectTypeId = TYPEID_ITEM;
m_entityFragments.Add(WowCS::EntityFragment::Tag_Item, false);
m_slot = 0;
uState = ITEM_NEW;
uQueuePos = -1;
m_container = nullptr;
m_lootGenerated = false;
mb_in_trade = false;
m_paidMoney = 0;
m_paidExtendedCost = 0;
m_randomBonusListId = 0;
m_gemScalingLevels = { };
memset(&_bonusData, 0, sizeof(_bonusData));
}
Item::~Item() = default;
bool Item::Create(ObjectGuid::LowType guidlow, uint32 itemId, ItemContext context, Player const* owner)
{
Object::_Create(ObjectGuid::Create(guidlow));
SetEntry(itemId);
SetObjectScale(1.0f);
if (owner)
{
SetOwnerGUID(owner->GetGUID());
SetContainedIn(owner->GetGUID());
}
ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(itemId);
if (!itemProto)
return false;
_bonusData.Initialize(itemProto);
SetCount(1);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::MaxDurability), itemProto->MaxDurability);
SetDurability(itemProto->MaxDurability);
for (ItemEffectEntry const* effect : GetEffects())
SetSpellCharges(effect, effect->Charges);
SetExpiration(itemProto->GetDuration());
SetCreatePlayedTime(0);
SetCreateTime(GameTime::GetGameTime());
SetContext(context);
if (itemProto->GetArtifactID())
{
InitArtifactPowers(itemProto->GetArtifactID(), 0);
for (ArtifactAppearanceEntry const* artifactAppearance : sArtifactAppearanceStore)
{
if (ArtifactAppearanceSetEntry const* artifactAppearanceSet = sArtifactAppearanceSetStore.LookupEntry(artifactAppearance->ArtifactAppearanceSetID))
{
if (itemProto->GetArtifactID() != artifactAppearanceSet->ArtifactID)
continue;
if (!owner || !ConditionMgr::IsPlayerMeetingCondition(owner, artifactAppearance->UnlockPlayerConditionID))
continue;
SetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID, artifactAppearance->ID);
SetAppearanceModId(artifactAppearance->ItemAppearanceModifierID);
break;
}
}
CheckArtifactRelicSlotUnlock(owner ? owner : GetOwner());
}
return true;
}
std::string Item::GetNameForLocaleIdx(LocaleConstant locale) const
{
ItemTemplate const* itemTemplate = GetTemplate();
if (ItemNameDescriptionEntry const* suffix = sItemNameDescriptionStore.LookupEntry(_bonusData.Suffix))
return Trinity::StringFormat("{} {}", itemTemplate->GetName(locale), suffix->Description[locale]);
return itemTemplate->GetName(locale);
}
// Returns true if Item is a bag AND it is not empty.
// Returns false if Item is not a bag OR it is an empty bag.
bool Item::IsNotEmptyBag() const
{
if (Bag const* bag = ToBag())
return !bag->IsEmpty();
return false;
}
void Item::UpdateDuration(Player* owner, uint32 diff)
{
uint32 duration = m_itemData->Expiration;
if (!duration)
return;
TC_LOG_DEBUG("entities.player.items", "Item::UpdateDuration Item (Entry: {} Duration {} Diff {})", GetEntry(), duration, diff);
if (duration <= diff)
{
sScriptMgr->OnItemExpire(owner, GetTemplate());
owner->DestroyItem(GetBagSlot(), GetSlot(), true);
return;
}
SetExpiration(duration - diff);
SetState(ITEM_CHANGED, owner); // save new time in database
}
static uint32 FindSpellChargesSlot(BonusData const& bonusData, ItemEffectEntry const* effect)
{
static constexpr uint32 MaxSpellChargesSlot = UF::size();
if (!effect)
{
// return fist effect that has charges
for (uint32 i = 0; i < bonusData.EffectCount && i < MaxSpellChargesSlot; ++i)
if (bonusData.Effects[i] && bonusData.Effects[i]->Charges != 0)
return i;
return MaxSpellChargesSlot;
}
for (uint32 i = 0; i < bonusData.EffectCount && i < MaxSpellChargesSlot; ++i)
if (bonusData.Effects[i] == effect)
return i;
return MaxSpellChargesSlot;
}
int32 Item::GetSpellCharges(ItemEffectEntry const* effect /*= nullptr*/) const
{
uint32 slot = FindSpellChargesSlot(_bonusData, effect);
if (slot < m_itemData->SpellCharges.size())
return m_itemData->SpellCharges[slot];
return 0;
}
void Item::SetSpellCharges(ItemEffectEntry const* effect, int32 value)
{
uint32 slot = FindSpellChargesSlot(_bonusData, effect);
if (slot < m_itemData->SpellCharges.size())
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::SpellCharges, slot), value);
}
void Item::SaveToDB(CharacterDatabaseTransaction trans)
{
bool isInTransaction = bool(trans);
if (!isInTransaction)
trans = CharacterDatabase.BeginTransaction();
switch (uState)
{
case ITEM_NEW:
case ITEM_CHANGED:
{
uint8 index = 0;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(uState == ITEM_NEW ? CHAR_REP_ITEM_INSTANCE : CHAR_UPD_ITEM_INSTANCE);
stmt->setUInt32( index, GetEntry());
stmt->setUInt64(++index, GetOwnerGUID().GetCounter());
stmt->setUInt64(++index, GetCreator().GetCounter());
stmt->setUInt64(++index, GetGiftCreator().GetCounter());
stmt->setUInt32(++index, GetCount());
stmt->setUInt32(++index, m_itemData->Expiration);
std::ostringstream ssSpells;
for (uint8 i = 0; i < m_itemData->SpellCharges.size() && i < _bonusData.EffectCount; ++i)
ssSpells << m_itemData->SpellCharges[i] << ' ';
stmt->setString(++index, ssSpells.str());
stmt->setUInt32(++index, m_itemData->DynamicFlags);
std::ostringstream ssEnchants;
for (uint8 i = 0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
if (SpellItemEnchantmentEntry const* enchantment = sSpellItemEnchantmentStore.LookupEntry(GetEnchantmentId(EnchantmentSlot(i)));
enchantment && !enchantment->GetFlags().HasFlag(SpellItemEnchantmentFlags::DoNotSaveToDB))
{
ssEnchants << GetEnchantmentId(EnchantmentSlot(i)) << ' ';
ssEnchants << GetEnchantmentDuration(EnchantmentSlot(i)) << ' ';
ssEnchants << GetEnchantmentCharges(EnchantmentSlot(i)) << ' ';
}
else
{
ssEnchants << "0 0 0 ";
}
}
stmt->setString(++index, ssEnchants.str());
stmt->setUInt32(++index, m_randomBonusListId);
stmt->setUInt16(++index, m_itemData->Durability);
stmt->setUInt32(++index, m_itemData->CreatePlayedTime);
stmt->setInt64(++index, m_itemData->CreateTime);
stmt->setString(++index, m_text);
stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID));
stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA));
stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL));
stmt->setUInt32(++index, GetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID));
stmt->setUInt8(++index, uint8(m_itemData->Context));
std::ostringstream bonusListIDs;
for (int32 bonusListID : GetBonusListIDs())
bonusListIDs << bonusListID << ' ';
stmt->setString(++index, bonusListIDs.str());
stmt->setUInt64(++index, GetGUID().GetCounter());
trans->Append(stmt);
if ((uState == ITEM_CHANGED) && IsWrapped())
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GIFT_OWNER);
stmt->setUInt64(0, GetOwnerGUID().GetCounter());
stmt->setUInt64(1, GetGUID().GetCounter());
trans->Append(stmt);
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
if (m_itemData->Gems.size())
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_INSTANCE_GEMS);
stmt->setUInt64(0, GetGUID().GetCounter());
uint32 i = 0;
uint32 const gemFields = 4;
for (UF::SocketedGem const& gemData : m_itemData->Gems)
{
if (gemData.ItemID)
{
stmt->setUInt32(1 + i * gemFields, gemData.ItemID);
std::ostringstream gemBonusListIDs;
for (uint16 bonusListID : gemData.BonusListIDs)
if (bonusListID)
gemBonusListIDs << bonusListID << ' ';
stmt->setString(2 + i * gemFields, gemBonusListIDs.str());
stmt->setUInt8(3 + i * gemFields, gemData.Context);
stmt->setUInt32(4 + i * gemFields, m_gemScalingLevels[i]);
}
else
{
stmt->setUInt32(1 + i * gemFields, 0);
stmt->setString(2 + i * gemFields, ""sv);
stmt->setUInt8(3 + i * gemFields, 0);
stmt->setUInt32(4 + i * gemFields, 0);
}
++i;
}
for (; i < MAX_GEM_SOCKETS; ++i)
{
stmt->setUInt32(1 + i * gemFields, 0);
stmt->setString(2 + i * gemFields, ""sv);
stmt->setUInt8(3 + i * gemFields, 0);
stmt->setUInt32(4 + i * gemFields, 0);
}
trans->Append(stmt);
}
static ItemModifier const transmogMods[18] =
{
ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_1,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_2,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_3,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_4,
ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_5,
ITEM_MODIFIER_ENCHANT_ILLUSION_ALL_SPECS,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_1,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_2,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_3,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_4,
ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_5,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_ALL_SPECS,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_1,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_2,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_3,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_4,
ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_5
};
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_TRANSMOG);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
if (std::find_if(std::begin(transmogMods), std::end(transmogMods), [this](ItemModifier modifier) { return GetModifier(modifier) != 0; }) != std::end(transmogMods))
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_INSTANCE_TRANSMOG);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS));
stmt->setUInt32(2, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_1));
stmt->setUInt32(3, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_2));
stmt->setUInt32(4, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_3));
stmt->setUInt32(5, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_4));
stmt->setUInt32(6, GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_5));
stmt->setUInt32(7, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_ALL_SPECS));
stmt->setUInt32(8, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_1));
stmt->setUInt32(9, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_2));
stmt->setUInt32(10, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_3));
stmt->setUInt32(11, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_4));
stmt->setUInt32(12, GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_5));
stmt->setUInt32(13, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_ALL_SPECS));
stmt->setUInt32(14, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_1));
stmt->setUInt32(15, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_2));
stmt->setUInt32(16, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_3));
stmt->setUInt32(17, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_4));
stmt->setUInt32(18, GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_5));
trans->Append(stmt);
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT_POWERS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
if (GetTemplate()->GetArtifactID())
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_INSTANCE_ARTIFACT);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt64(1, m_itemData->ArtifactXP);
stmt->setUInt32(2, GetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID));
stmt->setUInt32(3, GetModifier(ITEM_MODIFIER_ARTIFACT_TIER));
trans->Append(stmt);
for (UF::ArtifactPower const& artifactPower : m_itemData->ArtifactPowers)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_INSTANCE_ARTIFACT_POWERS);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, artifactPower.ArtifactPowerID);
stmt->setUInt8(2, artifactPower.PurchasedRank);
trans->Append(stmt);
}
}
static ItemModifier const modifiersTable[] =
{
ITEM_MODIFIER_TIMEWALKER_LEVEL,
ITEM_MODIFIER_ARTIFACT_KNOWLEDGE_LEVEL
};
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_MODIFIERS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
if (std::find_if(std::begin(modifiersTable), std::end(modifiersTable), [this](ItemModifier modifier) { return GetModifier(modifier) != 0; }) != std::end(modifiersTable))
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_INSTANCE_MODIFIERS);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt32(1, GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL));
stmt->setUInt32(2, GetModifier(ITEM_MODIFIER_ARTIFACT_KNOWLEDGE_LEVEL));
trans->Append(stmt);
}
break;
}
case ITEM_REMOVED:
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_TRANSMOG);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT_POWERS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_MODIFIERS);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
if (IsWrapped())
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
}
if (!isInTransaction)
CharacterDatabase.CommitTransaction(trans);
// Delete the items if this is a container
if (m_loot && !m_loot->isLooted())
sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter());
delete this;
return;
}
case ITEM_UNCHANGED:
break;
}
SetState(ITEM_UNCHANGED);
if (!isInTransaction)
CharacterDatabase.CommitTransaction(trans);
}
bool Item::LoadFromDB(ObjectGuid::LowType guid, ObjectGuid ownerGuid, Field* fields, uint32 entry)
{
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
// SELECT guid, itemEntry, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomBonusListId, durability, playedTime, createTime, text,
// 14 15 16 17 18 19
// battlePetSpeciesId, battlePetBreedData, battlePetLevel, battlePetDisplayId, context, bonusListIDs,
// 20 21 22 23 24 25
// itemModifiedAppearanceAllSpecs, itemModifiedAppearanceSpec1, itemModifiedAppearanceSpec2, itemModifiedAppearanceSpec3, itemModifiedAppearanceSpec4, itemModifiedAppearanceSpec5,
// 26 27 28 29 30 31
// spellItemEnchantmentAllSpecs, spellItemEnchantmentSpec1, spellItemEnchantmentSpec2, spellItemEnchantmentSpec3, spellItemEnchantmentSpec4, spellItemEnchantmentSpec5,
// 32 33 34
// secondaryItemModifiedAppearanceAllSpecs, secondaryItemModifiedAppearanceSpec1, secondaryItemModifiedAppearanceSpec2,
// 35 36 37
// secondaryItemModifiedAppearanceSpec3, secondaryItemModifiedAppearanceSpec4, secondaryItemModifiedAppearanceSpec5,
// 38 39 40 41 42 43 44 45 46 47 48 49
// gemItemId1, gemBonuses1, gemContext1, gemScalingLevel1, gemItemId2, gemBonuses2, gemContext2, gemScalingLevel2, gemItemId3, gemBonuses3, gemContext3, gemScalingLevel3
// 50 51
// fixedScalingLevel, artifactKnowledgeLevel FROM item_instance
// create item before any checks for store correct guid
// and allow use "FSetState(ITEM_REMOVED); SaveToDB();" for deleting item from DB
Object::_Create(ObjectGuid::Create(guid));
// Set entry, MUST be before proto check
SetEntry(entry);
SetObjectScale(1.0f);
ItemTemplate const* proto = GetTemplate();
if (!proto)
{
TC_LOG_ERROR("entities.item", "Invalid entry {} for item {}. Refusing to load.", GetEntry(), GetGUID().ToString());
return false;
}
_bonusData.Initialize(proto);
// set owner (not if item is only loaded for gbank/auction/mail
if (!ownerGuid.IsEmpty())
SetOwnerGUID(ownerGuid);
uint32 itemFlags = fields[7].GetUInt32();
bool need_save = false; // need explicit save data at load fixes
if (uint64 creator = fields[2].GetUInt64())
{
if (!(itemFlags & ITEM_FIELD_FLAG_CHILD))
SetCreator(ObjectGuid::Create(creator));
else
SetCreator(ObjectGuid::Create(creator));
}
if (uint64 giftCreator = fields[3].GetUInt64())
SetGiftCreator(ObjectGuid::Create(giftCreator));
SetCount(fields[4].GetUInt32());
uint32 duration = fields[5].GetUInt32();
SetExpiration(duration);
// update duration if need, and remove if not need
if ((proto->GetDuration() == 0) != (duration == 0))
{
SetExpiration(proto->GetDuration());
need_save = true;
}
ReplaceAllItemFlags(ItemFieldFlags(itemFlags));
uint32 durability = fields[10].GetUInt16();
SetDurability(durability);
// update max durability (and durability) if need
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::MaxDurability), proto->MaxDurability);
// do not overwrite durability for wrapped items
if (durability > proto->MaxDurability && !IsWrapped())
{
SetDurability(proto->MaxDurability);
need_save = true;
}
SetCreatePlayedTime(fields[11].GetUInt32());
SetCreateTime(fields[12].GetInt64());
SetText(fields[13].GetString());
SetModifier(ITEM_MODIFIER_BATTLE_PET_SPECIES_ID, fields[14].GetUInt32());
SetModifier(ITEM_MODIFIER_BATTLE_PET_BREED_DATA, fields[15].GetUInt32());
SetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL, fields[16].GetUInt16());
SetModifier(ITEM_MODIFIER_BATTLE_PET_DISPLAY_ID, fields[17].GetUInt32());
SetContext(ItemContext(fields[18].GetUInt8()));
std::vector bonusListString = Trinity::Tokenize(fields[19].GetStringView(), ' ', false);
std::vector bonusListIDs;
bonusListIDs.reserve(bonusListString.size());
for (std::string_view token : bonusListString)
if (Optional bonusListID = Trinity::StringTo(token))
bonusListIDs.push_back(*bonusListID);
SetBonuses(std::move(bonusListIDs));
// load charges after bonuses, they can add more item effects
std::vector tokens = Trinity::Tokenize(fields[6].GetStringView(), ' ', false);
for (uint8 i = 0; i < m_itemData->SpellCharges.size() && i < _bonusData.EffectCount && i < tokens.size(); ++i)
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::SpellCharges, i), Trinity::StringTo(tokens[i]).value_or(0));
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS, fields[20].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_1, fields[21].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_2, fields[22].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_3, fields[23].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_4, fields[24].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_SPEC_5, fields[25].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_ALL_SPECS, fields[26].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_1, fields[27].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_2, fields[28].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_3, fields[29].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_4, fields[30].GetUInt32());
SetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_SPEC_5, fields[31].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_ALL_SPECS, fields[32].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_1, fields[33].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_2, fields[34].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_3, fields[35].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_4, fields[36].GetUInt32());
SetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_SPEC_5, fields[37].GetUInt32());
uint32 const gemFields = 4;
ItemDynamicFieldGems gemData[MAX_GEM_SOCKETS];
memset(gemData, 0, sizeof(gemData));
for (uint32 i = 0; i < MAX_GEM_SOCKETS; ++i)
{
gemData[i].ItemId = fields[38 + i * gemFields].GetUInt32();
std::vector gemBonusListIDs = Trinity::Tokenize(fields[39 + i * gemFields].GetStringView(), ' ', false);
uint32 b = 0;
for (std::string_view token : gemBonusListIDs)
if (Optional bonusListID = Trinity::StringTo(token))
gemData[i].BonusListIDs[b++] = *bonusListID;
gemData[i].Context = fields[40 + i * gemFields].GetUInt8();
if (gemData[i].ItemId)
SetGem(i, &gemData[i], fields[41 + i * gemFields].GetUInt32());
}
SetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL, fields[50].GetUInt32());
SetModifier(ITEM_MODIFIER_ARTIFACT_KNOWLEDGE_LEVEL, fields[51].GetUInt32());
// Enchants must be loaded after all other bonus/scaling data
std::vector enchantmentTokens = Trinity::Tokenize(fields[8].GetStringView(), ' ', false);
if (enchantmentTokens.size() == MAX_ENCHANTMENT_SLOT * MAX_ENCHANTMENT_OFFSET)
{
for (uint32 i = 0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
auto enchantmentField = m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Enchantment, i);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::ID), Trinity::StringTo(enchantmentTokens[i * MAX_ENCHANTMENT_OFFSET + 0]).value_or(0));
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Duration), Trinity::StringTo(enchantmentTokens[i * MAX_ENCHANTMENT_OFFSET + 1]).value_or(0));
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Charges), Trinity::StringTo(enchantmentTokens[i * MAX_ENCHANTMENT_OFFSET + 2]).value_or(0));
}
}
m_randomBonusListId = fields[9].GetUInt32();
// Remove bind flag for items vs BIND_NONE set
if (IsSoulBound() && GetBonding() == BIND_NONE)
{
RemoveItemFlag(ITEM_FIELD_FLAG_SOULBOUND);
need_save = true;
}
if (need_save) // normal item changed state set not work at loading
{
uint8 index = 0;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_INSTANCE_ON_LOAD);
stmt->setUInt32(index++, m_itemData->Expiration);
stmt->setUInt32(index++, m_itemData->DynamicFlags);
stmt->setUInt32(index++, m_itemData->Durability);
stmt->setUInt64(index++, guid);
CharacterDatabase.Execute(stmt);
}
return true;
}
void Item::LoadAdditionalDataFromDB(Player const* owner, ItemAdditionalLoadInfo* addionalData)
{
if (GetTemplate()->GetArtifactID() && addionalData->Artifact)
LoadArtifactData(owner, addionalData->Artifact->Xp, addionalData->Artifact->ArtifactAppearanceId,
addionalData->Artifact->ArtifactTierId, addionalData->Artifact->ArtifactPowers);
if (addionalData->AzeriteItem)
if (AzeriteItem* azeriteItem = ToAzeriteItem())
azeriteItem->LoadAzeriteItemData(owner, *addionalData->AzeriteItem);
if (addionalData->AzeriteEmpoweredItem)
if (AzeriteEmpoweredItem* azeriteEmpoweredItem = ToAzeriteEmpoweredItem())
azeriteEmpoweredItem->LoadAzeriteEmpoweredItemData(owner, *addionalData->AzeriteEmpoweredItem);
}
void Item::LoadArtifactData(Player const* owner, uint64 xp, uint32 artifactAppearanceId, uint32 artifactTier, std::vector& powers)
{
for (uint8 i = 0; i <= artifactTier; ++i)
InitArtifactPowers(GetTemplate()->GetArtifactID(), i);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ArtifactXP), xp);
SetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID, artifactAppearanceId);
SetModifier(ITEM_MODIFIER_ARTIFACT_TIER, artifactTier);
if (ArtifactAppearanceEntry const* artifactAppearance = sArtifactAppearanceStore.LookupEntry(artifactAppearanceId))
SetAppearanceModId(artifactAppearance->ItemAppearanceModifierID);
uint8 totalPurchasedRanks = 0;
for (ArtifactPowerData& power : powers)
{
power.CurrentRankWithBonus += power.PurchasedRank;
totalPurchasedRanks += power.PurchasedRank;
ArtifactPowerEntry const* artifactPower = sArtifactPowerStore.AssertEntry(power.ArtifactPowerId);
for (uint32 e = SOCK_ENCHANTMENT_SLOT; e <= SOCK_ENCHANTMENT_SLOT_3; ++e)
{
if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(GetEnchantmentId(EnchantmentSlot(e))))
{
for (uint32 i = 0; i < MAX_ITEM_ENCHANTMENT_EFFECTS; ++i)
{
switch (enchant->Effect[i])
{
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_TYPE:
if (uint32(artifactPower->Label) == enchant->EffectArg[i])
power.CurrentRankWithBonus += enchant->EffectPointsMin[i];
break;
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_ID:
if (artifactPower->ID == enchant->EffectArg[i])
power.CurrentRankWithBonus += enchant->EffectPointsMin[i];
break;
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_PICKER:
if (_bonusData.GemRelicType[e - SOCK_ENCHANTMENT_SLOT] != -1)
{
ArtifactPowerPickerEntry const* artifactPowerPicker = sArtifactPowerPickerStore.LookupEntry(enchant->EffectArg[i]);
if (artifactPowerPicker && owner && ConditionMgr::IsPlayerMeetingCondition(owner, artifactPowerPicker->PlayerConditionID))
if (artifactPower->Label == _bonusData.GemRelicType[e - SOCK_ENCHANTMENT_SLOT])
power.CurrentRankWithBonus += enchant->EffectPointsMin[i];
}
break;
default:
break;
}
}
}
}
SetArtifactPower(power.ArtifactPowerId, power.PurchasedRank, power.CurrentRankWithBonus);
}
for (ArtifactPowerData& power : powers)
{
ArtifactPowerEntry const* scaledArtifactPowerEntry = sArtifactPowerStore.AssertEntry(power.ArtifactPowerId);
if (!(scaledArtifactPowerEntry->Flags & ARTIFACT_POWER_FLAG_SCALES_WITH_NUM_POWERS))
continue;
SetArtifactPower(power.ArtifactPowerId, power.PurchasedRank, totalPurchasedRanks + 1);
}
CheckArtifactRelicSlotUnlock(owner);
}
void Item::CheckArtifactRelicSlotUnlock(Player const* owner)
{
if (!owner)
return;
uint8 artifactId = GetTemplate()->GetArtifactID();
if (!artifactId)
return;
for (ArtifactUnlockEntry const* artifactUnlock : sArtifactUnlockStore)
if (artifactUnlock->ArtifactID == artifactId)
if (owner->MeetPlayerCondition(artifactUnlock->PlayerConditionID))
AddBonuses(artifactUnlock->ItemBonusListID);
}
/*static*/
void Item::DeleteFromDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_GEMS);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_TRANSMOG);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_ARTIFACT_POWERS);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_MODIFIERS);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT);
stmt->setUInt64(0, itemGuid);
CharacterDatabase.ExecuteOrAppend(trans, stmt);
}
void Item::DeleteFromDB(CharacterDatabaseTransaction trans)
{
DeleteFromDB(trans, GetGUID().GetCounter());
// Delete the items if this is a container
if (m_loot && !m_loot->isLooted())
sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter());
}
/*static*/
void Item::DeleteFromInventoryDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM);
stmt->setUInt64(0, itemGuid);
trans->Append(stmt);
}
void Item::DeleteFromInventoryDB(CharacterDatabaseTransaction trans)
{
DeleteFromInventoryDB(trans, GetGUID().GetCounter());
}
ItemTemplate const* Item::GetTemplate() const
{
return sObjectMgr->GetItemTemplate(GetEntry());
}
Player* Item::GetOwner() const
{
return ObjectAccessor::FindPlayer(GetOwnerGUID());
}
// Just a "legacy shortcut" for proto->GetSkill()
uint32 Item::GetSkill()
{
ItemTemplate const* proto = GetTemplate();
return proto->GetSkill();
}
void Item::SetItemRandomBonusList(ItemRandomBonusListId bonusListId)
{
if (!bonusListId)
return;
AddBonuses(bonusListId);
}
void Item::SetState(ItemUpdateState state, Player* forplayer)
{
if (uState == ITEM_NEW && state == ITEM_REMOVED)
{
// pretend the item never existed
if (forplayer)
{
RemoveItemFromUpdateQueueOf(this, forplayer);
forplayer->DeleteRefundReference(GetGUID());
}
delete this;
return;
}
if (state != ITEM_UNCHANGED)
{
// new items must stay in new state until saved
if (uState != ITEM_NEW)
uState = state;
if (forplayer)
AddItemToUpdateQueueOf(this, forplayer);
}
else
{
// unset in queue
// the item must be removed from the queue manually
uQueuePos = -1;
uState = ITEM_UNCHANGED;
}
}
void AddItemToUpdateQueueOf(Item* item, Player* player)
{
if (item->IsInUpdateQueue())
return;
ASSERT(player != nullptr);
if (player->GetGUID() != item->GetOwnerGUID())
{
TC_LOG_DEBUG("entities.player.items", "AddItemToUpdateQueueOf - Owner's guid ({}) and player's guid ({}) don't match!",
item->GetOwnerGUID().ToString(), player->GetGUID().ToString());
return;
}
if (player->m_itemUpdateQueueBlocked)
return;
player->m_itemUpdateQueue.push_back(item);
item->uQueuePos = player->m_itemUpdateQueue.size() - 1;
}
void RemoveItemFromUpdateQueueOf(Item* item, Player* player)
{
if (!item->IsInUpdateQueue())
return;
ASSERT(player != nullptr);
if (player->GetGUID() != item->GetOwnerGUID())
{
TC_LOG_DEBUG("entities.player.items", "RemoveItemFromUpdateQueueOf - Owner's guid ({}) and player's guid ({}) don't match!",
item->GetOwnerGUID().ToString(), player->GetGUID().ToString());
return;
}
if (player->m_itemUpdateQueueBlocked)
return;
player->m_itemUpdateQueue[item->uQueuePos] = nullptr;
item->uQueuePos = -1;
}
uint8 Item::GetBagSlot() const
{
return m_container ? m_container->GetSlot() : uint8(INVENTORY_SLOT_BAG_0);
}
bool Item::IsEquipped() const
{
return !IsInBag() && (m_slot < EQUIPMENT_SLOT_END
|| (m_slot >= PROFESSION_SLOT_START && m_slot < PROFESSION_SLOT_END));
}
bool Item::CanBeTraded(bool mail, bool trade) const
{
if (m_lootGenerated)
return false;
if ((!mail || !IsBoundAccountWide()) && (IsSoulBound() && (!IsBOPTradeable() || !trade)))
return false;
if (IsBag() && (Player::IsBagPos(GetPos()) || !ToBag()->IsEmpty()))
return false;
if (Player* owner = GetOwner())
{
if (owner->CanUnequipItem(GetPos(), false) != EQUIP_ERR_OK)
return false;
if (owner->GetLootGUID() == GetGUID())
return false;
}
if (IsBoundByEnchant())
return false;
return true;
}
void Item::SetCount(uint32 value)
{
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::StackCount), value);
if (Player* player = GetOwner())
{
if (TradeData* tradeData = player->GetTradeData())
{
TradeSlots slot = tradeData->GetTradeSlotForItem(GetGUID());
if (slot != TRADE_SLOT_INVALID)
tradeData->SetItem(slot, this, true);
}
}
}
uint64 Item::CalculateDurabilityRepairCost(float discount) const
{
uint32 maxDurability = m_itemData->MaxDurability;
if (!maxDurability)
return 0;
uint32 curDurability = m_itemData->Durability;
ASSERT(maxDurability >= curDurability);
uint32 lostDurability = maxDurability - curDurability;
if (!lostDurability)
return 0;
ItemTemplate const* itemTemplate = GetTemplate();
DurabilityCostsEntry const* durabilityCost = sDurabilityCostsStore.LookupEntry(GetItemLevel(GetOwner()));
if (!durabilityCost)
return 0;
uint32 durabilityQualityEntryId = (GetQuality() + 1) * 2;
DurabilityQualityEntry const* durabilityQualityEntry = sDurabilityQualityStore.LookupEntry(durabilityQualityEntryId);
if (!durabilityQualityEntry)
return 0;
uint32 dmultiplier = 0;
if (itemTemplate->GetClass() == ITEM_CLASS_WEAPON)
dmultiplier = durabilityCost->WeaponSubClassCost[itemTemplate->GetSubClass()];
else if (itemTemplate->GetClass() == ITEM_CLASS_ARMOR)
dmultiplier = durabilityCost->ArmorSubClassCost[itemTemplate->GetSubClass()];
uint64 cost = std::round(lostDurability * dmultiplier * durabilityQualityEntry->Data * GetRepairCostMultiplier());
cost = uint64(cost * discount * sWorld->getRate(RATE_REPAIRCOST));
if (cost == 0) // Fix for ITEM_QUALITY_ARTIFACT
cost = 1;
return cost;
}
bool Item::HasEnchantRequiredSkill(Player const* player) const
{
// Check all enchants for required skill
for (uint32 enchant_slot = PERM_ENCHANTMENT_SLOT; enchant_slot < MAX_ENCHANTMENT_SLOT; ++enchant_slot)
if (uint32 enchant_id = GetEnchantmentId(EnchantmentSlot(enchant_slot)))
if (SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id))
if (enchantEntry->RequiredSkillID && player->GetSkillValue(enchantEntry->RequiredSkillID) < enchantEntry->RequiredSkillRank)
return false;
return true;
}
uint32 Item::GetEnchantRequiredLevel() const
{
uint32 level = 0;
// Check all enchants for required level
for (uint32 enchant_slot = PERM_ENCHANTMENT_SLOT; enchant_slot < MAX_ENCHANTMENT_SLOT; ++enchant_slot)
if (uint32 enchant_id = GetEnchantmentId(EnchantmentSlot(enchant_slot)))
if (SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id))
if (enchantEntry->MinLevel > level)
level = enchantEntry->MinLevel;
return level;
}
bool Item::IsBoundByEnchant() const
{
// Check all enchants for soulbound
for (uint32 enchant_slot = PERM_ENCHANTMENT_SLOT; enchant_slot < MAX_ENCHANTMENT_SLOT; ++enchant_slot)
if (uint32 enchant_id = GetEnchantmentId(EnchantmentSlot(enchant_slot)))
if (SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id))
if (enchantEntry->GetFlags().HasFlag(SpellItemEnchantmentFlags::Soulbound))
return true;
return false;
}
InventoryResult Item::CanBeMergedPartlyWith(ItemTemplate const* proto) const
{
// not allow merge looting currently items
if (m_lootGenerated)
return EQUIP_ERR_LOOT_GONE;
// check item type
if (GetEntry() != proto->GetId())
return EQUIP_ERR_CANT_STACK;
// check free space (full stacks can't be target of merge
if (GetCount() >= proto->GetMaxStackSize())
return EQUIP_ERR_CANT_STACK;
return EQUIP_ERR_OK;
}
bool Item::IsFitToSpellRequirements(SpellInfo const* spellInfo) const
{
ItemTemplate const* proto = GetTemplate();
bool isEnchantSpell = spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM) || spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) || spellInfo->HasEffect(SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC);
if (spellInfo->EquippedItemClass != -1) // -1 == any item class
{
if (isEnchantSpell && proto->HasFlag(ITEM_FLAG3_CAN_STORE_ENCHANTS))
return true;
if (spellInfo->EquippedItemClass != int32(proto->GetClass()))
return false; // wrong item class
if (spellInfo->EquippedItemSubClassMask != 0) // 0 == any subclass
{
if ((spellInfo->EquippedItemSubClassMask & (1 << proto->GetSubClass())) == 0)
return false; // subclass not present in mask
}
}
if (isEnchantSpell && spellInfo->EquippedItemInventoryTypeMask != 0) // 0 == any inventory type
{
// Special case - accept weapon type for main and offhand requirements
if (proto->GetInventoryType() == INVTYPE_WEAPON &&
(spellInfo->EquippedItemInventoryTypeMask & (1 << INVTYPE_WEAPONMAINHAND) ||
spellInfo->EquippedItemInventoryTypeMask & (1 << INVTYPE_WEAPONOFFHAND)))
return true;
else if ((spellInfo->EquippedItemInventoryTypeMask & (1 << proto->GetInventoryType())) == 0)
return false; // inventory type not present in mask
}
return true;
}
void Item::SetEnchantment(EnchantmentSlot slot, uint32 id, uint32 duration, uint32 charges, ObjectGuid caster /*= ObjectGuid::Empty*/)
{
// Better lost small time at check in comparison lost time at item save to DB.
if ((GetEnchantmentId(slot) == id) && (GetEnchantmentDuration(slot) == duration) && (GetEnchantmentCharges(slot) == charges))
return;
Player* owner = GetOwner();
if (slot < MAX_INSPECTED_ENCHANTMENT_SLOT)
{
if (SpellItemEnchantmentEntry const* oldEnchant = sSpellItemEnchantmentStore.LookupEntry(GetEnchantmentId(slot)))
if (!oldEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::DoNotLog))
owner->GetSession()->SendEnchantmentLog(GetOwnerGUID(), ObjectGuid::Empty, GetGUID(), GetEntry(), oldEnchant->ID, slot);
if (SpellItemEnchantmentEntry const* newEnchant = sSpellItemEnchantmentStore.LookupEntry(id))
if (!newEnchant->GetFlags().HasFlag(SpellItemEnchantmentFlags::DoNotLog))
owner->GetSession()->SendEnchantmentLog(GetOwnerGUID(), caster, GetGUID(), GetEntry(), id, slot);
}
ApplyArtifactPowerEnchantmentBonuses(slot, GetEnchantmentId(slot), false, owner);
ApplyArtifactPowerEnchantmentBonuses(slot, id, true, owner);
auto enchantmentField = m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Enchantment, slot);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::ID), id);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Duration), duration);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Charges), charges);
SetState(ITEM_CHANGED, owner);
}
void Item::SetEnchantmentDuration(EnchantmentSlot slot, uint32 duration, Player* owner)
{
if (GetEnchantmentDuration(slot) == duration)
return;
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Enchantment, slot).ModifyValue(&UF::ItemEnchantment::Duration), duration);
SetState(ITEM_CHANGED, owner);
// Cannot use GetOwner() here, has to be passed as an argument to avoid freeze due to hashtable locking
}
void Item::SetEnchantmentCharges(EnchantmentSlot slot, uint32 charges)
{
if (GetEnchantmentCharges(slot) == charges)
return;
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Enchantment, slot).ModifyValue(&UF::ItemEnchantment::Charges), charges);
SetState(ITEM_CHANGED, GetOwner());
}
void Item::ClearEnchantment(EnchantmentSlot slot)
{
if (!GetEnchantmentId(slot))
return;
auto enchantmentField = m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Enchantment, slot);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::ID), 0);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Duration), 0);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Charges), 0);
SetUpdateFieldValue(enchantmentField.ModifyValue(&UF::ItemEnchantment::Inactive), 0);
SetState(ITEM_CHANGED, GetOwner());
}
UF::SocketedGem const* Item::GetGem(uint16 slot) const
{
ASSERT(slot < MAX_GEM_SOCKETS);
return slot < m_itemData->Gems.size() ? &m_itemData->Gems[slot] : nullptr;
}
void Item::SetGem(uint16 slot, ItemDynamicFieldGems const* gem, uint32 gemScalingLevel)
{
ASSERT(slot < MAX_GEM_SOCKETS);
m_gemScalingLevels[slot] = gemScalingLevel;
_bonusData.GemItemLevelBonus[slot] = 0;
if (ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(gem->ItemId))
{
if (GemPropertiesEntry const* gemProperties = sGemPropertiesStore.LookupEntry(gemTemplate->GetGemProperties()))
{
if (SpellItemEnchantmentEntry const* gemEnchant = sSpellItemEnchantmentStore.LookupEntry(gemProperties->EnchantId))
{
BonusData gemBonus;
gemBonus.Initialize(gemTemplate);
for (uint16 bonusListId : gem->BonusListIDs)
gemBonus.AddBonusList(bonusListId);
uint32 gemBaseItemLevel = gemTemplate->GetBaseItemLevel();
if (gemBonus.PlayerLevelToItemLevelCurveId)
if (uint32 scaledIlvl = uint32(sDB2Manager.GetCurveValueAt(gemBonus.PlayerLevelToItemLevelCurveId, gemScalingLevel)))
gemBaseItemLevel = scaledIlvl;
_bonusData.GemRelicType[slot] = gemBonus.RelicType;
for (uint32 i = 0; i < MAX_ITEM_ENCHANTMENT_EFFECTS; ++i)
{
switch (gemEnchant->Effect[i])
{
case ITEM_ENCHANTMENT_TYPE_BONUS_LIST_ID:
{
for (ItemBonusEntry const* itemBonus : ItemBonusMgr::GetItemBonuses(gemEnchant->EffectArg[i]))
if (itemBonus->Type == ITEM_BONUS_ITEM_LEVEL)
_bonusData.GemItemLevelBonus[slot] += itemBonus->Value[0];
break;
}
case ITEM_ENCHANTMENT_TYPE_BONUS_LIST_CURVE:
{
if (uint32 bonusListId = ItemBonusMgr::GetItemBonusListForItemLevelDelta(int16(sDB2Manager.GetCurveValueAt(CURVE_ID_ARTIFACT_RELIC_ITEM_LEVEL_BONUS, gemBaseItemLevel + gemBonus.ItemLevelBonus))))
for (ItemBonusEntry const* itemBonus : ItemBonusMgr::GetItemBonuses(bonusListId))
if (itemBonus->Type == ITEM_BONUS_ITEM_LEVEL)
_bonusData.GemItemLevelBonus[slot] += itemBonus->Value[0];
break;
}
default:
break;
}
}
}
}
}
auto gemField = m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Gems, slot);
SetUpdateFieldValue(gemField.ModifyValue(&UF::SocketedGem::ItemID), gem->ItemId);
SetUpdateFieldValue(gemField.ModifyValue(&UF::SocketedGem::Context), gem->Context);
for (uint32 i = 0; i < 16; ++i)
SetUpdateFieldValue(gemField.ModifyValue(&UF::SocketedGem::BonusListIDs, i), gem->BonusListIDs[i]);
}
bool Item::GemsFitSockets() const
{
for (uint32 gemSlot = 0; gemSlot < MAX_ITEM_PROTO_SOCKETS; ++gemSlot)
{
uint8 SocketColor = GetTemplate()->GetSocketColor(gemSlot);
if (!SocketColor) // no socket slot
continue;
if (gemSlot >= m_itemData->Gems.size()) // no gems on this socket
return false;
uint8 GemColor = 0;
uint32 gemid = m_itemData->Gems[gemSlot].ItemID;
if (gemid)
{
ItemTemplate const* gemProto = sObjectMgr->GetItemTemplate(gemid);
if (gemProto)
{
GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gemProto->GetGemProperties());
if (gemProperty)
GemColor = gemProperty->Type;
}
}
if (!(GemColor & SocketColorToGemTypeMask[SocketColor])) // bad gem color on this socket
return false;
}
return true;
}
uint8 Item::GetGemCountWithID(uint32 GemID) const
{
return uint8(std::count_if(m_itemData->Gems.begin(), m_itemData->Gems.end(), [GemID](UF::SocketedGem const& gemData)
{
return gemData.ItemID == int32(GemID);
}));
}
uint8 Item::GetGemCountWithLimitCategory(uint32 limitCategory) const
{
return uint8(std::count_if(m_itemData->Gems.begin(), m_itemData->Gems.end(), [limitCategory](UF::SocketedGem const& gemData)
{
ItemTemplate const* gemProto = sObjectMgr->GetItemTemplate(gemData.ItemID);
if (!gemProto)
return false;
return gemProto->GetItemLimitCategory() == limitCategory;
}));
}
bool Item::IsLimitedToAnotherMapOrZone(uint32 cur_mapId, uint32 cur_zoneId) const
{
ItemTemplate const* proto = GetTemplate();
return proto && ((proto->GetMap() && proto->GetMap() != cur_mapId) ||
((proto->GetArea(0) && proto->GetArea(0) != cur_zoneId) && (proto->GetArea(1) && proto->GetArea(1) != cur_zoneId)));
}
void Item::SendUpdateSockets()
{
WorldPackets::Item::SocketGemsSuccess socketGems;
socketGems.Item = GetGUID();
GetOwner()->GetSession()->SendPacket(socketGems.Write());
}
// Though the client has the information in the item's data field,
// we have to send SMSG_ITEM_TIME_UPDATE to display the remaining
// time.
void Item::SendTimeUpdate(Player* owner)
{
uint32 duration = m_itemData->Expiration;
if (!duration)
return;
WorldPackets::Item::ItemTimeUpdate itemTimeUpdate;
itemTimeUpdate.ItemGuid = GetGUID();
itemTimeUpdate.DurationLeft = duration;
owner->GetSession()->SendPacket(itemTimeUpdate.Write());
}
Item* Item::CreateItem(uint32 itemEntry, uint32 count, ItemContext context, Player const* player /*= nullptr*/, bool addDefaultBonuses /*= true*/)
{
if (count < 1)
return nullptr; //don't create item at zero count
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
if (proto)
{
if (count > proto->GetMaxStackSize())
count = proto->GetMaxStackSize();
ASSERT_NODEBUGINFO(count != 0, "proto->Stackable == 0 but checked at loading already");
Item* item = NewItemOrBag(proto);
if (item->Create(sObjectMgr->GetGenerator().Generate(), itemEntry, context, player))
{
item->SetCount(count);
if (addDefaultBonuses)
item->SetBonuses(ItemBonusMgr::GetBonusListsForItem(itemEntry, context));
return item;
}
else
delete item;
}
else
ABORT();
return nullptr;
}
Item* Item::CloneItem(uint32 count, Player const* player /*= nullptr*/) const
{
Item* newItem = CreateItem(GetEntry(), count, GetContext(), player, false);
if (!newItem)
return nullptr;
newItem->SetCreator(GetCreator());
newItem->SetGiftCreator(GetGiftCreator());
newItem->ReplaceAllItemFlags(ItemFieldFlags(*m_itemData->DynamicFlags) & ~(ITEM_FIELD_FLAG_REFUNDABLE | ITEM_FIELD_FLAG_BOP_TRADEABLE));
newItem->SetExpiration(m_itemData->Expiration);
newItem->SetBonuses(m_itemData->ItemBonusKey->BonusListIDs);
newItem->SetFixedLevel(GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL));
// player CAN be NULL in which case we must not update random properties because that accesses player's item update queue
if (player)
newItem->SetItemRandomBonusList(m_randomBonusListId);
return newItem;
}
bool Item::IsBindedNotWith(Player const* player) const
{
// not binded item
if (!IsSoulBound())
return false;
// own item
if (GetOwnerGUID() == player->GetGUID())
return false;
if (IsBOPTradeable())
if (allowedGUIDs.find(player->GetGUID()) != allowedGUIDs.end())
return false;
// BOA item case
if (IsBoundAccountWide())
return false;
return true;
}
void Item::BuildUpdate(UpdateDataMapType& data_map)
{
if (Player* owner = GetOwner())
BuildFieldsUpdate(owner, data_map);
ClearUpdateMask(false);
}
UF::UpdateFieldFlag Item::GetUpdateFieldFlagsFor(Player const* target) const
{
if (target->GetGUID() == GetOwnerGUID())
return UF::UpdateFieldFlag::Owner;
return UF::UpdateFieldFlag::None;
}
void Item::BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
m_objectData->WriteCreate(*data, flags, this, target);
m_itemData->WriteCreate(*data, flags, this, target);
}
void Item::BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
*data << uint32(m_values.GetChangedObjectTypeMask());
if (m_values.HasChanged(TYPEID_OBJECT))
m_objectData->WriteUpdate(*data, flags, this, target);
if (m_values.HasChanged(TYPEID_ITEM))
m_itemData->WriteUpdate(*data, flags, this, target);
}
void Item::BuildValuesUpdateWithFlag(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
UpdateMask valuesMask;
valuesMask.Set(TYPEID_ITEM);
*data << uint32(valuesMask.GetBlock(0));
UF::ItemData::Mask mask;
m_itemData->AppendAllowedFieldsMaskForFlag(mask, flags);
m_itemData->WriteUpdate(*data, mask, true, this, target);
}
void Item::BuildValuesUpdateForPlayerWithMask(UpdateData* data, UF::ObjectData::Mask const& requestedObjectMask,
UF::ItemData::Mask const& requestedItemMask, Player const* target) const
{
UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target);
UpdateMask valuesMask;
if (requestedObjectMask.IsAnySet())
valuesMask.Set(TYPEID_OBJECT);
UF::ItemData::Mask itemMask = requestedItemMask;
m_itemData->FilterDisallowedFieldsMaskForFlag(itemMask, flags);
if (itemMask.IsAnySet())
valuesMask.Set(TYPEID_ITEM);
ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
std::size_t sizePos = buffer.wpos();
buffer << uint32(0);
BuildEntityFragmentsForValuesUpdateForPlayerWithMask(&buffer, flags);
buffer << uint32(valuesMask.GetBlock(0));
if (valuesMask[TYPEID_OBJECT])
m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
if (valuesMask[TYPEID_ITEM])
m_itemData->WriteUpdate(buffer, itemMask, true, this, target);
buffer.put(sizePos, buffer.wpos() - sizePos - 4);
data->AddUpdateBlock();
}
void Item::ValuesUpdateForPlayerWithMaskSender::operator()(Player const* player) const
{
UpdateData udata(player->GetMapId());
WorldPacket packet;
Owner->BuildValuesUpdateForPlayerWithMask(&udata, ObjectMask.GetChangesMask(), ItemMask.GetChangesMask(), player);
udata.BuildPacket(&packet);
player->SendDirectMessage(&packet);
}
void Item::ClearUpdateMask(bool remove)
{
m_values.ClearChangesMask(&Item::m_itemData);
Object::ClearUpdateMask(remove);
}
bool Item::AddToObjectUpdate()
{
if (Player* owner = GetOwner())
{
owner->GetMap()->AddUpdateObject(this);
return true;
}
return false;
}
void Item::RemoveFromObjectUpdate()
{
if (Player* owner = GetOwner())
owner->GetMap()->RemoveUpdateObject(this);
}
void Item::SaveRefundDataToDB()
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE);
stmt->setUInt64(0, GetGUID().GetCounter());
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_REFUND_INSTANCE);
stmt->setUInt64(0, GetGUID().GetCounter());
stmt->setUInt64(1, GetRefundRecipient().GetCounter());
stmt->setUInt64(2, GetPaidMoney());
stmt->setUInt16(3, uint16(GetPaidExtendedCost()));
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
}
void Item::DeleteRefundDataFromDB(CharacterDatabaseTransaction* trans)
{
if (trans)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE);
stmt->setUInt64(0, GetGUID().GetCounter());
(*trans)->Append(stmt);
}
}
void Item::SetNotRefundable(Player* owner, bool changestate /*= true*/, CharacterDatabaseTransaction* trans /*= nullptr*/, bool addToCollection /*= true*/)
{
if (!IsRefundable())
return;
WorldPackets::Item::ItemExpirePurchaseRefund itemExpirePurchaseRefund;
itemExpirePurchaseRefund.ItemGUID = GetGUID();
owner->SendDirectMessage(itemExpirePurchaseRefund.Write());
RemoveItemFlag(ITEM_FIELD_FLAG_REFUNDABLE);
// Following is not applicable in the trading procedure
if (changestate)
SetState(ITEM_CHANGED, owner);
SetRefundRecipient(ObjectGuid::Empty);
SetPaidMoney(0);
SetPaidExtendedCost(0);
DeleteRefundDataFromDB(trans);
owner->DeleteRefundReference(GetGUID());
if (addToCollection)
owner->GetSession()->GetCollectionMgr()->AddItemAppearance(this);
}
uint32 Item::GetPlayedTime() const
{
return *m_itemData->CreatePlayedTime;
}
bool Item::IsRefundExpired() const
{
return *m_itemData->CreateTime + 2 * HOUR <= GameTime::GetGameTime();
}
void Item::SetSoulboundTradeable(GuidSet const& allowedLooters)
{
SetItemFlag(ITEM_FIELD_FLAG_BOP_TRADEABLE);
allowedGUIDs = allowedLooters;
}
void Item::ClearSoulboundTradeable(Player* currentOwner)
{
RemoveItemFlag(ITEM_FIELD_FLAG_BOP_TRADEABLE);
if (allowedGUIDs.empty())
return;
currentOwner->GetSession()->GetCollectionMgr()->AddItemAppearance(this);
allowedGUIDs.clear();
SetState(ITEM_CHANGED, currentOwner);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_BOP_TRADE);
stmt->setUInt64(0, GetGUID().GetCounter());
CharacterDatabase.Execute(stmt);
}
bool Item::CheckSoulboundTradeExpire()
{
// called from owner's update - GetOwner() MUST be valid
if (m_itemData->CreatePlayedTime + 4 * HOUR < GetOwner()->GetTotalPlayedTime())
{
ClearSoulboundTradeable(GetOwner());
return true; // remove from tradeable list
}
return false;
}
bool Item::IsValidTransmogrificationTarget() const
{
ItemTemplate const* proto = GetTemplate();
if (!proto)
return false;
if (proto->GetClass() != ITEM_CLASS_ARMOR &&
proto->GetClass() != ITEM_CLASS_WEAPON)
return false;
if (proto->GetClass() == ITEM_CLASS_WEAPON && proto->GetSubClass() == ITEM_SUBCLASS_WEAPON_FISHING_POLE)
return false;
if (proto->HasFlag(ITEM_FLAG2_NO_ALTER_ITEM_VISUAL))
return false;
return true;
}
enum class ItemTransmogrificationWeaponCategory : uint8
{
// Two-handed
MELEE_2H,
RANGED,
// One-handed
AXE_MACE_SWORD_1H,
DAGGER,
INVALID
};
static ItemTransmogrificationWeaponCategory GetTransmogrificationWeaponCategory(ItemTemplate const* proto)
{
if (proto->GetClass() == ITEM_CLASS_WEAPON)
{
switch (proto->GetSubClass())
{
case ITEM_SUBCLASS_WEAPON_AXE2:
case ITEM_SUBCLASS_WEAPON_MACE2:
case ITEM_SUBCLASS_WEAPON_SWORD2:
case ITEM_SUBCLASS_WEAPON_STAFF:
case ITEM_SUBCLASS_WEAPON_POLEARM:
return ItemTransmogrificationWeaponCategory::MELEE_2H;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_GUN:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
return ItemTransmogrificationWeaponCategory::RANGED;
case ITEM_SUBCLASS_WEAPON_AXE:
case ITEM_SUBCLASS_WEAPON_MACE:
case ITEM_SUBCLASS_WEAPON_SWORD:
case ITEM_SUBCLASS_WEAPON_WARGLAIVES:
case ITEM_SUBCLASS_WEAPON_FIST_WEAPON:
return ItemTransmogrificationWeaponCategory::AXE_MACE_SWORD_1H;
case ITEM_SUBCLASS_WEAPON_DAGGER:
return ItemTransmogrificationWeaponCategory::DAGGER;
default:
break;
}
}
return ItemTransmogrificationWeaponCategory::INVALID;
}
int32 const ItemTransmogrificationSlots[MAX_INVTYPE] =
{
-1, // INVTYPE_NON_EQUIP
EQUIPMENT_SLOT_HEAD, // INVTYPE_HEAD
-1, // INVTYPE_NECK
EQUIPMENT_SLOT_SHOULDERS, // INVTYPE_SHOULDERS
EQUIPMENT_SLOT_BODY, // INVTYPE_BODY
EQUIPMENT_SLOT_CHEST, // INVTYPE_CHEST
EQUIPMENT_SLOT_WAIST, // INVTYPE_WAIST
EQUIPMENT_SLOT_LEGS, // INVTYPE_LEGS
EQUIPMENT_SLOT_FEET, // INVTYPE_FEET
EQUIPMENT_SLOT_WRISTS, // INVTYPE_WRISTS
EQUIPMENT_SLOT_HANDS, // INVTYPE_HANDS
-1, // INVTYPE_FINGER
-1, // INVTYPE_TRINKET
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_WEAPON
EQUIPMENT_SLOT_OFFHAND, // INVTYPE_SHIELD
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_RANGED
EQUIPMENT_SLOT_BACK, // INVTYPE_CLOAK
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_2HWEAPON
-1, // INVTYPE_BAG
EQUIPMENT_SLOT_TABARD, // INVTYPE_TABARD
EQUIPMENT_SLOT_CHEST, // INVTYPE_ROBE
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_WEAPONMAINHAND
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_WEAPONOFFHAND
EQUIPMENT_SLOT_OFFHAND, // INVTYPE_HOLDABLE
-1, // INVTYPE_AMMO
-1, // INVTYPE_THROWN
EQUIPMENT_SLOT_MAINHAND, // INVTYPE_RANGEDRIGHT
-1, // INVTYPE_QUIVER
-1 // INVTYPE_RELIC
-1, // INVTYPE_PROFESSION_TOOL
-1, // INVTYPE_PROFESSION_GEAR
-1, // INVTYPE_EQUIPABLE_SPELL_OFFENSIVE
-1, // INVTYPE_EQUIPABLE_SPELL_UTILITY
-1, // INVTYPE_EQUIPABLE_SPELL_DEFENSIVE
-1 // INVTYPE_EQUIPABLE_SPELL_MOBILITY
};
bool Item::CanTransmogrifyItemWithItem(Item const* item, ItemModifiedAppearanceEntry const* itemModifiedAppearance)
{
ItemTemplate const* source = sObjectMgr->GetItemTemplate(itemModifiedAppearance->ItemID); // source
ItemTemplate const* target = item->GetTemplate(); // dest
if (!source || !target)
return false;
if (itemModifiedAppearance == item->GetItemModifiedAppearance())
return false;
if (!item->IsValidTransmogrificationTarget())
return false;
if (source->GetClass() != target->GetClass())
return false;
if (source->GetInventoryType() == INVTYPE_BAG ||
source->GetInventoryType() == INVTYPE_RELIC ||
source->GetInventoryType() == INVTYPE_FINGER ||
source->GetInventoryType() == INVTYPE_TRINKET ||
source->GetInventoryType() == INVTYPE_AMMO ||
source->GetInventoryType() == INVTYPE_QUIVER)
return false;
if (source->GetSubClass() != target->GetSubClass())
{
switch (source->GetClass())
{
case ITEM_CLASS_WEAPON:
if (GetTransmogrificationWeaponCategory(source) != GetTransmogrificationWeaponCategory(target))
return false;
break;
case ITEM_CLASS_ARMOR:
if (source->GetSubClass() != ITEM_SUBCLASS_ARMOR_COSMETIC)
return false;
if (source->GetInventoryType() != target->GetInventoryType())
if (ItemTransmogrificationSlots[source->GetInventoryType()] != ItemTransmogrificationSlots[target->GetInventoryType()])
return false;
break;
default:
return false;
}
}
return true;
}
uint32 Item::GetBuyPrice(Player const* owner, bool& standardPrice) const
{
return Item::GetBuyPrice(GetTemplate(), GetQuality(), GetItemLevel(owner), standardPrice);
}
uint32 Item::GetBuyPrice(ItemTemplate const* proto, uint32 quality, uint32 itemLevel, bool& standardPrice)
{
standardPrice = false;
if (proto->HasFlag(ITEM_FLAG2_OVERRIDE_GOLD_COST))
return proto->GetBuyPrice();
ImportPriceQualityEntry const* qualityPrice = sImportPriceQualityStore.LookupEntry(quality + 1);
if (!qualityPrice)
return 0;
ItemPriceBaseEntry const* basePrice = sItemPriceBaseStore.LookupEntry(itemLevel);
if (!basePrice)
return 0;
float qualityFactor = qualityPrice->Data;
float baseFactor = 0.0f;
uint32 inventoryType = proto->GetInventoryType();
if (inventoryType == INVTYPE_WEAPON ||
inventoryType == INVTYPE_2HWEAPON ||
inventoryType == INVTYPE_WEAPONMAINHAND ||
inventoryType == INVTYPE_WEAPONOFFHAND ||
inventoryType == INVTYPE_RANGED ||
inventoryType == INVTYPE_THROWN ||
inventoryType == INVTYPE_RANGEDRIGHT)
baseFactor = basePrice->Weapon;
else
baseFactor = basePrice->Armor;
if (inventoryType == INVTYPE_ROBE)
inventoryType = INVTYPE_CHEST;
if (proto->GetClass() == ITEM_CLASS_GEM && proto->GetSubClass() == ITEM_SUBCLASS_GEM_ARTIFACT_RELIC)
{
inventoryType = INVTYPE_WEAPON;
baseFactor = basePrice->Weapon / 3.0f;
}
float typeFactor = 0.0f;
int8 weapType = -1;
switch (inventoryType)
{
case INVTYPE_HEAD:
case INVTYPE_NECK:
case INVTYPE_SHOULDERS:
case INVTYPE_CHEST:
case INVTYPE_WAIST:
case INVTYPE_LEGS:
case INVTYPE_FEET:
case INVTYPE_WRISTS:
case INVTYPE_HANDS:
case INVTYPE_FINGER:
case INVTYPE_TRINKET:
case INVTYPE_CLOAK:
case INVTYPE_HOLDABLE:
{
ImportPriceArmorEntry const* armorPrice = sImportPriceArmorStore.LookupEntry(inventoryType);
if (!armorPrice)
return 0;
switch (proto->GetSubClass())
{
case ITEM_SUBCLASS_ARMOR_MISCELLANEOUS:
case ITEM_SUBCLASS_ARMOR_CLOTH:
typeFactor = armorPrice->ClothModifier;
break;
case ITEM_SUBCLASS_ARMOR_LEATHER:
typeFactor = armorPrice->LeatherModifier;
break;
case ITEM_SUBCLASS_ARMOR_MAIL:
typeFactor = armorPrice->ChainModifier;
break;
case ITEM_SUBCLASS_ARMOR_PLATE:
typeFactor = armorPrice->PlateModifier;
break;
default:
typeFactor = 1.0f;
break;
}
break;
}
case INVTYPE_SHIELD:
{
ImportPriceShieldEntry const* shieldPrice = sImportPriceShieldStore.LookupEntry(2);
if (!shieldPrice)
return 0;
typeFactor = shieldPrice->Data;
break;
}
case INVTYPE_WEAPONMAINHAND:
weapType = 0;
break;
case INVTYPE_WEAPONOFFHAND:
weapType = 1;
break;
case INVTYPE_WEAPON:
weapType = 2;
break;
case INVTYPE_2HWEAPON:
weapType = 3;
break;
case INVTYPE_RANGED:
case INVTYPE_RANGEDRIGHT:
case INVTYPE_RELIC:
weapType = 4;
break;
default:
return proto->GetBuyPrice();
}
if (weapType != -1)
{
ImportPriceWeaponEntry const* weaponPrice = sImportPriceWeaponStore.LookupEntry(weapType + 1);
if (!weaponPrice)
return 0;
typeFactor = weaponPrice->Data;
}
standardPrice = true;
return uint32(proto->GetPriceVariance() * typeFactor * baseFactor * qualityFactor * proto->GetPriceRandomValue());
}
uint32 Item::GetSellPrice(Player const* owner) const
{
return Item::GetSellPrice(GetTemplate(), GetQuality(), GetItemLevel(owner));
}
uint32 Item::GetSellPrice(ItemTemplate const* proto, uint32 quality, uint32 itemLevel)
{
if (proto->HasFlag(ITEM_FLAG2_OVERRIDE_GOLD_COST))
return proto->GetSellPrice();
else
{
bool standardPrice;
uint32 cost = Item::GetBuyPrice(proto, quality, itemLevel, standardPrice);
if (standardPrice)
{
if (ItemClassEntry const* classEntry = sDB2Manager.GetItemClassByOldEnum(proto->GetClass()))
{
uint32 buyCount = std::max(proto->GetBuyCount(), 1u);
return cost * classEntry->PriceModifier / buyCount;
}
return 0;
}
else
return proto->GetSellPrice();
}
return 0;
}
uint32 Item::GetItemLevel(Player const* owner) const
{
ItemTemplate const* itemTemplate = GetTemplate();
uint32 minItemLevel = owner->m_unitData->MinItemLevel;
uint32 minItemLevelCutoff = owner->m_unitData->MinItemLevelCutoff;
bool pvpBonus = owner->IsUsingPvpItemLevels();
uint32 maxItemLevel = pvpBonus && itemTemplate->HasFlag(ITEM_FLAG3_IGNORE_ITEM_LEVEL_CAP_IN_PVP) ? 0 : owner->m_unitData->MaxItemLevel;
uint32 azeriteLevel = 0;
if (AzeriteItem const* azeriteItem = ToAzeriteItem())
azeriteLevel = azeriteItem->GetEffectiveLevel();
return Item::GetItemLevel(itemTemplate, _bonusData, owner->GetLevel(), GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL),
minItemLevel, minItemLevelCutoff, maxItemLevel, pvpBonus, azeriteLevel);
}
uint32 Item::GetItemLevel(ItemTemplate const* itemTemplate, BonusData const& bonusData, uint32 level, uint32 fixedLevel,
uint32 minItemLevel, uint32 minItemLevelCutoff, uint32 maxItemLevel, bool pvpBonus, uint32 azeriteLevel)
{
if (!itemTemplate)
return MIN_ITEM_LEVEL;
uint32 itemLevel = itemTemplate->GetBaseItemLevel();
if (AzeriteLevelInfoEntry const* azeriteLevelInfo = sAzeriteLevelInfoStore.LookupEntry(azeriteLevel))
itemLevel = azeriteLevelInfo->ItemLevel;
if (bonusData.PlayerLevelToItemLevelCurveId)
{
if (fixedLevel)
level = fixedLevel;
else if (Optional levels = sDB2Manager.GetContentTuningData(bonusData.ContentTuningId, 0, true))
level = std::min(std::max(int16(level), levels->MinLevel), levels->MaxLevel);
itemLevel = uint32(sDB2Manager.GetCurveValueAt(bonusData.PlayerLevelToItemLevelCurveId, level));
}
itemLevel += bonusData.ItemLevelBonus;
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
itemLevel += bonusData.GemItemLevelBonus[i];
uint32 itemLevelBeforeUpgrades = itemLevel;
if (pvpBonus)
itemLevel += sDB2Manager.GetPvpItemLevelBonus(itemTemplate->GetId());
if (itemTemplate->GetInventoryType() != INVTYPE_NON_EQUIP)
{
if (minItemLevel && (!minItemLevelCutoff || itemLevelBeforeUpgrades >= minItemLevelCutoff) && itemLevel < minItemLevel)
itemLevel = minItemLevel;
if (maxItemLevel && itemLevel > maxItemLevel)
itemLevel = maxItemLevel;
}
return std::min(std::max(itemLevel, uint32(MIN_ITEM_LEVEL)), uint32(MAX_ITEM_LEVEL));
}
float Item::GetItemStatValue(uint32 index, Player const* owner) const
{
ASSERT(index < MAX_ITEM_PROTO_STATS);
switch (GetItemStatType(index))
{
case ITEM_MOD_CORRUPTION:
case ITEM_MOD_CORRUPTION_RESISTANCE:
return _bonusData.StatPercentEditor[index];
default:
break;
}
uint32 itemLevel = GetItemLevel(owner);
if (float randomPropPoints = GetRandomPropertyPoints(itemLevel, GetQuality(), GetTemplate()->GetInventoryType(), GetTemplate()->GetSubClass()))
{
float statValue = float(_bonusData.StatPercentEditor[index] * randomPropPoints) * 0.0001f;
if (GtItemSocketCostPerLevelEntry const* gtCost = sItemSocketCostPerLevelGameTable.GetRow(itemLevel))
statValue -= float(int32(_bonusData.ItemStatSocketCostMultiplier[index] * gtCost->SocketCost));
return statValue;
}
return 0.0f;
}
Optional Item::GetDisenchantLootId() const
{
if (!_bonusData.CanDisenchant)
return {};
if (_bonusData.DisenchantLootId)
return _bonusData.DisenchantLootId;
// ignore temporary item level scaling (pvp or timewalking)
uint32 itemLevel = GetItemLevel(GetTemplate(), _bonusData, _bonusData.RequiredLevel, GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL), 0, 0, 0, false, 0);
ItemDisenchantLootEntry const* disenchantLoot = GetBaseDisenchantLoot(GetTemplate(), GetQuality(), itemLevel);
if (!disenchantLoot)
return {};
return disenchantLoot->ID;
}
Optional Item::GetDisenchantSkillRequired() const
{
if (!_bonusData.CanDisenchant)
return {};
// ignore temporary item level scaling (pvp or timewalking)
uint32 itemLevel = GetItemLevel(GetTemplate(), _bonusData, _bonusData.RequiredLevel, GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL), 0, 0, 0, false, 0);
ItemDisenchantLootEntry const* disenchantLoot = GetBaseDisenchantLoot(GetTemplate(), GetQuality(), itemLevel);
if (!disenchantLoot)
return {};
return disenchantLoot->SkillRequired;
}
ItemDisenchantLootEntry const* Item::GetBaseDisenchantLoot(ItemTemplate const* itemTemplate, uint32 quality, uint32 itemLevel)
{
if (itemTemplate->HasFlag(ITEM_FLAG_CONJURED) || itemTemplate->HasFlag(ITEM_FLAG_NO_DISENCHANT) || itemTemplate->GetBonding() == BIND_QUEST)
return nullptr;
if (itemTemplate->GetArea(0) || itemTemplate->GetArea(1) || itemTemplate->GetMap() || itemTemplate->GetMaxStackSize() > 1)
return nullptr;
if (!Item::GetSellPrice(itemTemplate, quality, itemLevel) && !sDB2Manager.HasItemCurrencyCost(itemTemplate->GetId()))
return nullptr;
uint32 itemClass = itemTemplate->GetClass();
int8 itemSubClass = itemTemplate->GetSubClass();
uint8 expansion = itemTemplate->GetRequiredExpansion();
for (ItemDisenchantLootEntry const* disenchant : sItemDisenchantLootStore)
{
if (disenchant->Class != itemClass)
continue;
if (disenchant->Subclass >= 0 && itemSubClass)
continue;
if (disenchant->Quality != quality)
continue;
if (disenchant->MinLevel > itemLevel || disenchant->MaxLevel < itemLevel)
continue;
if (disenchant->ExpansionID != -2 && disenchant->ExpansionID != expansion)
continue;
return disenchant;
}
return nullptr;
}
uint32 Item::GetDisplayId(Player const* owner) const
{
uint32 itemModifiedAppearanceId = GetModifier(AppearanceModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!itemModifiedAppearanceId)
itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS);
if (ItemModifiedAppearanceEntry const* transmog = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId))
if (ItemAppearanceEntry const* itemAppearance = sItemAppearanceStore.LookupEntry(transmog->ItemAppearanceID))
return itemAppearance->ItemDisplayInfoID;
return sDB2Manager.GetItemDisplayId(GetEntry(), GetAppearanceModId());
}
ItemModifiedAppearanceEntry const* Item::GetItemModifiedAppearance() const
{
return sDB2Manager.GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID);
}
uint32 Item::GetModifier(ItemModifier modifier) const
{
int32 modifierIndex = m_itemData->Modifiers->Values.FindIndexIf([modifier](UF::ItemMod mod)
{
return mod.Type == modifier;
});
if (modifierIndex != -1)
return m_itemData->Modifiers->Values[modifierIndex].Value;
return 0;
}
void Item::SetModifier(ItemModifier modifier, uint32 value)
{
int32 modifierIndex = m_itemData->Modifiers->Values.FindIndexIf([modifier](UF::ItemMod mod)
{
return mod.Type == modifier;
});
if (value)
{
if (modifierIndex == -1)
{
UF::ItemMod& mod = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::Modifiers).ModifyValue(&UF::ItemModList::Values));
mod.Value = value;
mod.Type = modifier;
}
else
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::Modifiers)
.ModifyValue(&UF::ItemModList::Values, modifierIndex)
.ModifyValue(&UF::ItemMod::Value), value);
}
else
{
if (modifierIndex == -1)
return;
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::Modifiers).ModifyValue(&UF::ItemModList::Values), modifierIndex);
}
}
uint32 Item::GetVisibleEntry(Player const* owner) const
{
uint32 itemModifiedAppearanceId = GetModifier(AppearanceModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!itemModifiedAppearanceId)
itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS);
if (ItemModifiedAppearanceEntry const* transmog = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId))
return transmog->ItemID;
return GetEntry();
}
uint16 Item::GetVisibleAppearanceModId(Player const* owner) const
{
uint32 itemModifiedAppearanceId = GetModifier(AppearanceModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!itemModifiedAppearanceId)
itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS);
if (ItemModifiedAppearanceEntry const* transmog = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId))
return transmog->ItemAppearanceModifierID;
return uint16(GetAppearanceModId());
}
uint32 Item::GetVisibleModifiedAppearanceId(Player const* owner) const
{
uint32 itemModifiedAppearanceId = GetModifier(AppearanceModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!itemModifiedAppearanceId)
itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_APPEARANCE_ALL_SPECS);
if (!itemModifiedAppearanceId)
if (ItemModifiedAppearanceEntry const* itemModifiedAppearance = GetItemModifiedAppearance())
itemModifiedAppearanceId = itemModifiedAppearance->ID;
return itemModifiedAppearanceId;
}
int32 Item::GetVisibleSecondaryModifiedAppearanceId(Player const* owner) const
{
uint32 itemModifiedAppearanceId = GetModifier(SecondaryAppearanceModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!itemModifiedAppearanceId)
itemModifiedAppearanceId = GetModifier(ITEM_MODIFIER_TRANSMOG_SECONDARY_APPEARANCE_ALL_SPECS);
return itemModifiedAppearanceId;
}
uint32 Item::GetVisibleEnchantmentId(Player const* owner) const
{
uint32 enchantmentId = GetModifier(IllusionModifierSlotBySpec[owner->GetActiveTalentGroup()]);
if (!enchantmentId)
enchantmentId = GetModifier(ITEM_MODIFIER_ENCHANT_ILLUSION_ALL_SPECS);
if (!enchantmentId)
enchantmentId = GetEnchantmentId(PERM_ENCHANTMENT_SLOT);
return enchantmentId;
}
uint16 Item::GetVisibleItemVisual(Player const* owner) const
{
if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(GetVisibleEnchantmentId(owner)))
return enchant->ItemVisual;
return 0;
}
void Item::AddBonuses(uint32 bonusListID)
{
if (std::find(GetBonusListIDs().begin(), GetBonusListIDs().end(), int32(bonusListID)) != GetBonusListIDs().end())
return;
WorldPackets::Item::ItemBonusKey itemBonusKey;
itemBonusKey.ItemID = GetEntry();
itemBonusKey.BonusListIDs = GetBonusListIDs();
itemBonusKey.BonusListIDs.push_back(bonusListID);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemBonusKey), std::move(itemBonusKey));
for (ItemBonusEntry const* bonus : ItemBonusMgr::GetItemBonuses(bonusListID))
_bonusData.AddBonus(bonus->Type, bonus->Value);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemAppearanceModID), _bonusData.AppearanceModID);
}
void Item::SetBonuses(std::vector bonusListIDs)
{
WorldPackets::Item::ItemBonusKey itemBonusKey;
itemBonusKey.ItemID = GetEntry();
itemBonusKey.BonusListIDs = std::move(bonusListIDs);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemBonusKey), std::move(itemBonusKey));
for (int32 bonusListID : GetBonusListIDs())
_bonusData.AddBonusList(bonusListID);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemAppearanceModID), _bonusData.AppearanceModID);
}
void Item::ClearBonuses()
{
WorldPackets::Item::ItemBonusKey itemBonusKey;
itemBonusKey.ItemID = GetEntry();
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemBonusKey), std::move(itemBonusKey));
_bonusData.Initialize(GetTemplate());
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemAppearanceModID), _bonusData.AppearanceModID);
}
bool Item::IsArtifactDisabled() const
{
if (ArtifactEntry const* artifact = sArtifactStore.LookupEntry(GetTemplate()->GetArtifactID()))
return artifact->ArtifactCategoryID != 2; // fishing artifact
return true;
}
int32 Item::GetArtifactPowerIndex(uint32 artifactPowerId) const
{
return m_itemData->ArtifactPowers.FindIndexIf([artifactPowerId](UF::ArtifactPower const& artifactPower)
{
return uint32(artifactPower.ArtifactPowerID) == artifactPowerId;
});
}
UF::ArtifactPower const* Item::GetArtifactPower(uint32 artifactPowerId) const
{
int32 index = GetArtifactPowerIndex(artifactPowerId);
if (index >= 0)
return &m_itemData->ArtifactPowers[index];
return nullptr;
}
void Item::AddArtifactPower(ArtifactPowerData const* artifactPower)
{
UF::ArtifactPower& powerField = AddDynamicUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ArtifactPowers));
powerField.ArtifactPowerID = artifactPower->ArtifactPowerId;
powerField.PurchasedRank = artifactPower->PurchasedRank;
powerField.CurrentRankWithBonus = artifactPower->CurrentRankWithBonus;
}
void Item::SetArtifactPower(uint16 artifactPowerId, uint8 purchasedRank, uint8 currentRankWithBonus)
{
int32 index = GetArtifactPowerIndex(artifactPowerId);
if (index >= 0)
{
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::ArtifactPowers, index)
.ModifyValue(&UF::ArtifactPower::PurchasedRank), purchasedRank);
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::ArtifactPowers, index)
.ModifyValue(&UF::ArtifactPower::CurrentRankWithBonus), currentRankWithBonus);
}
}
void Item::InitArtifactPowers(uint8 artifactId, uint8 artifactTier)
{
std::unordered_set knownPowers;
for (UF::ArtifactPower const& power : m_itemData->ArtifactPowers)
knownPowers.insert(power.ArtifactPowerID);
for (ArtifactPowerEntry const* artifactPower : sDB2Manager.GetArtifactPowers(artifactId))
{
if (artifactPower->Tier != artifactTier)
continue;
if (knownPowers.contains(artifactPower->ID))
continue;
ArtifactPowerData powerData;
powerData.ArtifactPowerId = artifactPower->ID;
powerData.PurchasedRank = 0;
powerData.CurrentRankWithBonus = (artifactPower->Flags & ARTIFACT_POWER_FLAG_FIRST) == ARTIFACT_POWER_FLAG_FIRST ? 1 : 0;
AddArtifactPower(&powerData);
}
}
uint32 Item::GetTotalUnlockedArtifactPowers() const
{
uint32 purchased = GetTotalPurchasedArtifactPowers();
uint64 artifactXp = m_itemData->ArtifactXP;
uint32 currentArtifactTier = GetModifier(ITEM_MODIFIER_ARTIFACT_TIER);
uint32 extraUnlocked = 0;
do
{
uint64 xpCost = 0;
if (GtArtifactLevelXPEntry const* cost = sArtifactLevelXPGameTable.GetRow(purchased + extraUnlocked + 1))
xpCost = uint64(currentArtifactTier == MAX_ARTIFACT_TIER ? cost->XP2 : cost->XP);
if (artifactXp < xpCost)
break;
artifactXp -= xpCost;
++extraUnlocked;
} while (true);
return purchased + extraUnlocked;
}
uint32 Item::GetTotalPurchasedArtifactPowers() const
{
uint32 purchasedRanks = 0;
for (UF::ArtifactPower const& power : m_itemData->ArtifactPowers)
purchasedRanks += power.PurchasedRank;
return purchasedRanks;
}
void Item::ApplyArtifactPowerEnchantmentBonuses(EnchantmentSlot slot, uint32 enchantId, bool apply, Player* owner)
{
if (SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId))
{
for (uint32 i = 0; i < MAX_ITEM_ENCHANTMENT_EFFECTS; ++i)
{
switch (enchant->Effect[i])
{
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_TYPE:
for (uint32 artifactPowerIndex = 0; artifactPowerIndex < m_itemData->ArtifactPowers.size(); ++artifactPowerIndex)
{
UF::ArtifactPower const& artifactPower = m_itemData->ArtifactPowers[artifactPowerIndex];
if (uint32(sArtifactPowerStore.AssertEntry(artifactPower.ArtifactPowerID)->Label) == enchant->EffectArg[i])
{
uint8 newRank = artifactPower.CurrentRankWithBonus;
if (apply)
newRank += enchant->EffectPointsMin[i];
else
newRank -= enchant->EffectPointsMin[i];
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::ArtifactPowers, artifactPowerIndex)
.ModifyValue(&UF::ArtifactPower::CurrentRankWithBonus), newRank);
if (IsEquipped())
if (ArtifactPowerRankEntry const* artifactPowerRank = sDB2Manager.GetArtifactPowerRank(artifactPower.ArtifactPowerID, newRank ? newRank - 1 : 0))
owner->ApplyArtifactPowerRank(this, artifactPowerRank, newRank != 0);
}
}
break;
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_BY_ID:
{
if (int32 artifactPowerIndex = GetArtifactPowerIndex(enchant->EffectArg[i]); artifactPowerIndex >= 0)
{
uint8 newRank = m_itemData->ArtifactPowers[artifactPowerIndex].CurrentRankWithBonus;
if (apply)
newRank += enchant->EffectPointsMin[i];
else
newRank -= enchant->EffectPointsMin[i];
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::ArtifactPowers, artifactPowerIndex)
.ModifyValue(&UF::ArtifactPower::CurrentRankWithBonus), newRank);
if (IsEquipped())
if (ArtifactPowerRankEntry const* artifactPowerRank = sDB2Manager.GetArtifactPowerRank(m_itemData->ArtifactPowers[artifactPowerIndex].ArtifactPowerID, newRank ? newRank - 1 : 0))
owner->ApplyArtifactPowerRank(this, artifactPowerRank, newRank != 0);
}
break;
}
case ITEM_ENCHANTMENT_TYPE_ARTIFACT_POWER_BONUS_RANK_PICKER:
if (slot >= SOCK_ENCHANTMENT_SLOT && slot <= SOCK_ENCHANTMENT_SLOT_3 && _bonusData.GemRelicType[slot - SOCK_ENCHANTMENT_SLOT] != -1)
{
if (ArtifactPowerPickerEntry const* artifactPowerPicker = sArtifactPowerPickerStore.LookupEntry(enchant->EffectArg[i]))
{
if (ConditionMgr::IsPlayerMeetingCondition(owner, artifactPowerPicker->PlayerConditionID))
{
for (uint32 artifactPowerIndex = 0; artifactPowerIndex < m_itemData->ArtifactPowers.size(); ++artifactPowerIndex)
{
UF::ArtifactPower const& artifactPower = m_itemData->ArtifactPowers[artifactPowerIndex];
if (sArtifactPowerStore.AssertEntry(artifactPower.ArtifactPowerID)->Label == _bonusData.GemRelicType[slot - SOCK_ENCHANTMENT_SLOT])
{
uint8 newRank = artifactPower.CurrentRankWithBonus;
if (apply)
newRank += enchant->EffectPointsMin[i];
else
newRank -= enchant->EffectPointsMin[i];
SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData)
.ModifyValue(&UF::ItemData::ArtifactPowers, artifactPowerIndex)
.ModifyValue(&UF::ArtifactPower::CurrentRankWithBonus), newRank);
if (IsEquipped())
if (ArtifactPowerRankEntry const* artifactPowerRank = sDB2Manager.GetArtifactPowerRank(artifactPower.ArtifactPowerID, newRank ? newRank - 1 : 0))
owner->ApplyArtifactPowerRank(this, artifactPowerRank, newRank != 0);
}
}
}
}
}
break;
default:
break;
}
}
}
}
void Item::CopyArtifactDataFromParent(Item* parent)
{
memcpy(_bonusData.GemItemLevelBonus, parent->GetBonus()->GemItemLevelBonus, sizeof(_bonusData.GemItemLevelBonus));
SetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID, parent->GetModifier(ITEM_MODIFIER_ARTIFACT_APPEARANCE_ID));
SetAppearanceModId(parent->GetAppearanceModId());
}
void Item::GiveArtifactXp(uint64 amount, Item* sourceItem, uint32 artifactCategoryId)
{
Player* owner = GetOwner();
if (!owner)
return;
if (artifactCategoryId)
{
uint32 artifactKnowledgeLevel = 1;
if (sourceItem && sourceItem->GetModifier(ITEM_MODIFIER_ARTIFACT_KNOWLEDGE_LEVEL))
artifactKnowledgeLevel = sourceItem->GetModifier(ITEM_MODIFIER_ARTIFACT_KNOWLEDGE_LEVEL);
if (GtArtifactKnowledgeMultiplierEntry const* artifactKnowledge = sArtifactKnowledgeMultiplierGameTable.GetRow(artifactKnowledgeLevel))
amount = uint64(amount * artifactKnowledge->Multiplier);
if (amount >= 5000)
amount = 50 * (amount / 50);
else if (amount >= 1000)
amount = 25 * (amount / 25);
else if (amount >= 50)
amount = 5 * (amount / 5);
}
SetArtifactXP(m_itemData->ArtifactXP + amount);
WorldPackets::Artifact::ArtifactXpGain artifactXpGain;
artifactXpGain.ArtifactGUID = GetGUID();
artifactXpGain.Amount = amount;
owner->SendDirectMessage(artifactXpGain.Write());
SetState(ITEM_CHANGED, owner);
owner->UpdateCriteria(CriteriaType::EarnArtifactXP, amount);
}
void Item::SetFixedLevel(uint8 level)
{
if (!_bonusData.HasFixedLevel || GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL))
return;
if (_bonusData.PlayerLevelToItemLevelCurveId)
{
if (Optional levels = sDB2Manager.GetContentTuningData(_bonusData.ContentTuningId, 0, true))
level = std::min(std::max(int16(level), levels->MinLevel), levels->MaxLevel);
SetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL, level);
}
}
int32 Item::GetRequiredLevel() const
{
int32 fixedLevel = GetModifier(ITEM_MODIFIER_TIMEWALKER_LEVEL);
if (_bonusData.RequiredLevelCurve)
return sDB2Manager.GetCurveValueAt(_bonusData.RequiredLevelCurve, fixedLevel);
if (_bonusData.RequiredLevelOverride)
return _bonusData.RequiredLevelOverride;
if (_bonusData.HasFixedLevel && _bonusData.PlayerLevelToItemLevelCurveId)
return fixedLevel;
return _bonusData.RequiredLevel;
}
std::string Item::GetDebugInfo() const
{
std::stringstream sstr;
sstr << Object::GetDebugInfo() << "\n"
<< std::boolalpha
<< "Owner: " << GetOwnerGUID().ToString() << " Count: " << GetCount()
<< " BagSlot: " << std::to_string(GetBagSlot()) << " Slot: " << std::to_string(GetSlot()) << " Equipped: " << IsEquipped();
return sstr.str();
}
void BonusData::Initialize(ItemTemplate const* proto)
{
Quality = proto->GetQuality();
ItemLevelBonus = 0;
RequiredLevel = proto->GetBaseRequiredLevel();
for (uint32 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
{
ItemStatType[i] = proto->GetStatModifierBonusStat(i);
StatPercentEditor[i] = proto->GetStatPercentEditor(i);
ItemStatSocketCostMultiplier[i] = proto->GetStatPercentageOfSocket(i);
}
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
{
SocketColor[i] = proto->GetSocketColor(i);
GemItemLevelBonus[i] = 0;
GemRelicType[i] = -1;
GemRelicRankBonus[i] = 0;
}
Bonding = proto->GetBonding();
AppearanceModID = 0;
RepairCostMultiplier = 1.0f;
ContentTuningId = proto->GetScalingStatContentTuning();
PlayerLevelToItemLevelCurveId = proto->GetPlayerLevelToItemLevelCurveId();
DisenchantLootId = 0;
RelicType = -1;
HasFixedLevel = false;
RequiredLevelOverride = 0;
AzeriteTierUnlockSetId = 0;
if (AzeriteEmpoweredItemEntry const* azeriteEmpoweredItem = sDB2Manager.GetAzeriteEmpoweredItem(proto->GetId()))
AzeriteTierUnlockSetId = azeriteEmpoweredItem->AzeriteTierUnlockSetID;
Suffix = 0;
RequiredLevelCurve = 0;
EffectCount = 0;
for (ItemEffectEntry const* itemEffect : proto->Effects)
Effects[EffectCount++] = itemEffect;
for (std::size_t i = EffectCount; i < Effects.size(); ++i)
Effects[i] = nullptr;
CanDisenchant = !proto->HasFlag(ITEM_FLAG_NO_DISENCHANT);
CanScrap = proto->HasFlag(ITEM_FLAG4_SCRAPABLE);
_state.SuffixPriority = std::numeric_limits::max();
_state.AppearanceModPriority = std::numeric_limits::max();
_state.DisenchantLootPriority = std::numeric_limits::max();
_state.ScalingStatDistributionPriority = std::numeric_limits::max();
_state.AzeriteTierUnlockSetPriority = std::numeric_limits::max();
_state.RequiredLevelCurvePriority = std::numeric_limits::max();
_state.HasQualityBonus = false;
}
void BonusData::Initialize(WorldPackets::Item::ItemInstance const& itemInstance)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemInstance.ItemID);
if (!proto)
return;
Initialize(proto);
if (itemInstance.ItemBonus)
for (uint32 bonusListID : itemInstance.ItemBonus->BonusListIDs)
AddBonusList(bonusListID);
}
void BonusData::AddBonusList(uint32 bonusListId)
{
for (ItemBonusEntry const* bonus : ItemBonusMgr::GetItemBonuses(bonusListId))
AddBonus(bonus->Type, bonus->Value);
}
void BonusData::AddBonus(uint32 type, std::array const& values)
{
switch (type)
{
case ITEM_BONUS_ITEM_LEVEL:
ItemLevelBonus += values[0];
break;
case ITEM_BONUS_STAT:
{
uint32 statIndex = 0;
for (statIndex = 0; statIndex < MAX_ITEM_PROTO_STATS; ++statIndex)
if (ItemStatType[statIndex] == values[0] || ItemStatType[statIndex] == -1)
break;
if (statIndex < MAX_ITEM_PROTO_STATS)
{
ItemStatType[statIndex] = values[0];
StatPercentEditor[statIndex] += values[1];
}
break;
}
case ITEM_BONUS_QUALITY:
if (!_state.HasQualityBonus)
{
Quality = static_cast(values[0]);
_state.HasQualityBonus = true;
}
else if (Quality < static_cast(values[0]))
Quality = static_cast(values[0]);
break;
case ITEM_BONUS_SUFFIX:
if (values[1] < _state.SuffixPriority)
{
Suffix = static_cast(values[0]);
_state.SuffixPriority = values[1];
}
break;
case ITEM_BONUS_SOCKET:
{
uint32 socketCount = values[0];
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS && socketCount; ++i)
{
if (!SocketColor[i])
{
SocketColor[i] = values[1];
--socketCount;
}
}
break;
}
case ITEM_BONUS_APPEARANCE:
if (values[1] < _state.AppearanceModPriority)
{
AppearanceModID = static_cast(values[0]);
_state.AppearanceModPriority = values[1];
}
break;
case ITEM_BONUS_REQUIRED_LEVEL:
RequiredLevel += values[0];
break;
case ITEM_BONUS_REPAIR_COST_MULTIPLIER:
RepairCostMultiplier *= static_cast(values[0]) * 0.01f;
break;
case ITEM_BONUS_SCALING_STAT_DISTRIBUTION:
case ITEM_BONUS_SCALING_STAT_DISTRIBUTION_FIXED:
if (values[1] < _state.ScalingStatDistributionPriority)
{
ContentTuningId = static_cast(values[2]);
PlayerLevelToItemLevelCurveId = static_cast(values[3]);
_state.ScalingStatDistributionPriority = values[1];
HasFixedLevel = type == ITEM_BONUS_SCALING_STAT_DISTRIBUTION_FIXED;
}
break;
case ITEM_BONUS_DISENCHANT_LOOT_ID:
if (values[1] < _state.DisenchantLootPriority)
{
DisenchantLootId = values[0];
_state.DisenchantLootPriority = values[1];
}
break;
case ITEM_BONUS_BONDING:
Bonding = ItemBondingType(values[0]);
break;
case ITEM_BONUS_RELIC_TYPE:
RelicType = values[0];
break;
case ITEM_BONUS_OVERRIDE_REQUIRED_LEVEL:
RequiredLevelOverride = values[0];
break;
case ITEM_BONUS_AZERITE_TIER_UNLOCK_SET:
if (values[1] < _state.AzeriteTierUnlockSetPriority)
{
AzeriteTierUnlockSetId = values[0];
_state.AzeriteTierUnlockSetPriority = values[1];
}
break;
case ITEM_BONUS_OVERRIDE_CAN_DISENCHANT:
CanDisenchant = values[0] != 0;
break;
case ITEM_BONUS_OVERRIDE_CAN_SCRAP:
CanScrap = values[0] != 0;
break;
case ITEM_BONUS_ITEM_EFFECT_ID:
if (ItemEffectEntry const* itemEffect = sItemEffectStore.LookupEntry(values[0]))
Effects[EffectCount++] = itemEffect;
break;
case ITEM_BONUS_REQUIRED_LEVEL_CURVE:
if (values[2] < _state.RequiredLevelCurvePriority)
{
RequiredLevelCurve = values[0];
_state.RequiredLevelCurvePriority = values[2];
if (values[1])
ContentTuningId = static_cast(values[1]);
}
break;
}
}