diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index 78faddb7d13..fb2c63929a3 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -75,8 +75,8 @@ struct ItemSparseEntry uint32 ContainerSlots; // 20 int32 ItemStatType[MAX_ITEM_PROTO_STATS]; // 21 - 30 uint32 ItemStatValue[MAX_ITEM_PROTO_STATS]; // 31 - 40 - int32 ItemStatUnk1[MAX_ITEM_PROTO_STATS]; // 41 - 50 - int32 ItemStatUnk2[MAX_ITEM_PROTO_STATS]; // 51 - 60 + int32 ItemStatAllocation[MAX_ITEM_PROTO_STATS]; // 41 - 50 + int32 ItemStatSocketCostMultiplier[MAX_ITEM_PROTO_STATS]; // 51 - 60 uint32 ScalingStatDistribution; // 61 uint32 DamageType; // 62 uint32 Delay; // 63 diff --git a/src/server/game/DataStores/DB2Utility.cpp b/src/server/game/DataStores/DB2Utility.cpp index e9b483a766c..ba07ec55b43 100644 --- a/src/server/game/DataStores/DB2Utility.cpp +++ b/src/server/game/DataStores/DB2Utility.cpp @@ -87,10 +87,10 @@ void DB2Utilities::WriteItemSparseDbReply(DB2Storage const& /*s buffer << int32(proto->ItemStat[x].ItemStatValue); for (uint32 x = 0; x < MAX_ITEM_PROTO_STATS; ++x) - buffer << int32(proto->ItemStat[x].ItemStatUnk1); + buffer << int32(proto->ItemStat[x].ItemStatAllocation); for (uint32 x = 0; x < MAX_ITEM_PROTO_STATS; ++x) - buffer << int32(proto->ItemStat[x].ItemStatUnk2); + buffer << int32(proto->ItemStat[x].ItemStatSocketCostMultiplier); buffer << uint32(proto->ScalingStatDistribution); buffer << uint32(proto->DamageType); diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index c8cf4136353..2f9da16b27f 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -1338,3 +1338,254 @@ bool IsInArea(uint32 objectAreaId, uint32 areaId) return false; } + +uint32 ItemTemplate::GetEffectiveArmor(Player* owner) const +{ + if (Quality > ITEM_QUALITY_ARTIFACT) + return 0; + + uint32 level = ItemLevel; + if (owner != nullptr) + { + uint32 maxItemLevel = owner->GetUInt32Value(UNIT_FIELD_MAXITEMLEVEL); + if (maxItemLevel != 0 && level > maxItemLevel) + level = maxItemLevel; + } + + if (Class != ITEM_CLASS_ARMOR || SubClass != ITEM_SUBCLASS_ARMOR_SHIELD) + { + ItemArmorQualityEntry const* armorQuality = sItemArmorQualityStore.LookupEntry(level); + ItemArmorTotalEntry const* armorTotal = sItemArmorTotalStore.LookupEntry(level); + if (!armorQuality || !armorTotal) + return 0; + + uint32 invType = InventoryType; + if (invType == INVTYPE_ROBE) + invType = INVTYPE_CHEST; + + ArmorLocationEntry const* location = sArmorLocationStore.LookupEntry(invType); + if (!location) + return 0; + + if (SubClass < ITEM_SUBCLASS_ARMOR_CLOTH || SubClass > ITEM_SUBCLASS_ARMOR_PLATE) + return 0; + + return uint32(armorQuality->Value[Quality] * armorTotal->Value[SubClass - ITEM_SUBCLASS_ARMOR_CLOTH] * location->Value[SubClass - ITEM_SUBCLASS_ARMOR_CLOTH] + 0.5f); + } + + ItemArmorShieldEntry const* shield = sItemArmorShieldStore.LookupEntry(level); + if (!shield) + return 0; + + return uint32(shield->Value[Quality] + 0.5f); +} + +bool ItemTemplate::GetWeaponDamage(Player* owner, float& minValue, float& maxValue, float& dps) const +{ + minValue = maxValue = 0.0f; + if (Class != ITEM_CLASS_WEAPON || Quality > ITEM_QUALITY_ARTIFACT) + return false; + + uint32 level = ItemLevel; + if (owner != nullptr) + { + uint32 maxItemLevel = owner->GetUInt32Value(UNIT_FIELD_MAXITEMLEVEL); + if (maxItemLevel != 0 && level > maxItemLevel) + level = maxItemLevel; + } + + DBCStorage* store = nullptr; + + switch (InventoryType) + { + case INVTYPE_AMMO: + store = &sItemDamageAmmoStore; + break; + case INVTYPE_2HWEAPON: + if (Flags2 & ITEM_FLAG2_CASTER_WEAPON) + store = &sItemDamageTwoHandCasterStore; + else + store = &sItemDamageTwoHandStore; + break; + case INVTYPE_RANGED: + case INVTYPE_THROWN: + case INVTYPE_RANGEDRIGHT: + switch (SubClass) + { + case ITEM_SUBCLASS_WEAPON_WAND: + store = &sItemDamageWandStore; + break; + case ITEM_SUBCLASS_WEAPON_THROWN: + store = &sItemDamageThrownStore; + break; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + store = &sItemDamageRangedStore; + break; + default: + return false; + } + break; + case INVTYPE_WEAPON: + case INVTYPE_WEAPONMAINHAND: + case INVTYPE_WEAPONOFFHAND: + if (Flags2 & ITEM_FLAG2_CASTER_WEAPON) + store = &sItemDamageOneHandCasterStore; + else + store = &sItemDamageOneHandStore; + break; + default: + return false; + } + + if (!store) + return false; + + ItemDamageEntry const* damageInfo = store->LookupEntry(level); + if (!damageInfo) + return false; + + dps = damageInfo->DPS[Quality]; + + float avgDamage = Delay * damageInfo->DPS[Quality] * 0.001f; + float scaled_stat = std::floor((StatScalingFactor * 0.5f + 1.0f) * avgDamage + 0.5f); + if (Delay != 0 && ArmorDamageModifier != 0.0f) + { + float invMsDelay = 1000.0f / float(Delay); + + float v16 = (invMsDelay * ((1.0f - (StatScalingFactor * 0.5f)) * avgDamage)) + ArmorDamageModifier; + v16 = std::max(v16, 1.0f); + + minValue = (1.0f / invMsDelay) * v16; + + maxValue = (1.0f / invMsDelay) * (((1000.0f / float(Delay)) * scaled_stat) + ArmorDamageModifier); + } + else + { + maxValue = scaled_stat; + minValue = (1.0f - (StatScalingFactor * 0.5f)) * avgDamage; + } + + return true; +} + +uint32 GetItemScalingModifier(uint32 maxIlvl, ItemQualities quality, InventoryType invType) +{ + // Believe it or not, yes + uint32 suffixFactor = -1; + switch (invType) + { + // Items of that type don`t have points + case INVTYPE_NON_EQUIP: + case INVTYPE_BAG: + case INVTYPE_TABARD: + case INVTYPE_AMMO: + case INVTYPE_QUIVER: + case INVTYPE_RELIC: + break; + // Select point coefficient + case INVTYPE_HEAD: + case INVTYPE_BODY: + case INVTYPE_CHEST: + case INVTYPE_LEGS: + case INVTYPE_2HWEAPON: + case INVTYPE_ROBE: + suffixFactor = 0; + break; + case INVTYPE_SHOULDERS: + case INVTYPE_WAIST: + case INVTYPE_FEET: + case INVTYPE_HANDS: + case INVTYPE_TRINKET: + suffixFactor = 1; + break; + case INVTYPE_NECK: + case INVTYPE_WRISTS: + case INVTYPE_FINGER: + case INVTYPE_SHIELD: + case INVTYPE_CLOAK: + case INVTYPE_HOLDABLE: + suffixFactor = 2; + break; + case INVTYPE_WEAPON: + case INVTYPE_WEAPONMAINHAND: + case INVTYPE_WEAPONOFFHAND: + suffixFactor = 3; + break; + case INVTYPE_RANGED: + case INVTYPE_THROWN: + case INVTYPE_RANGEDRIGHT: + suffixFactor = 4; + break; + } + + if (suffixFactor > 4) + return 0; + + RandomPropertiesPointsEntry const* randPropEntry = sRandomPropertiesPointsStore.LookupEntry(maxIlvl); + if (!randPropEntry) + return 0; + + switch (quality) + { + case ITEM_QUALITY_UNCOMMON: + return randPropEntry->UncommonPropertiesPoints[suffixFactor]; + case ITEM_QUALITY_RARE: + return randPropEntry->RarePropertiesPoints[suffixFactor]; + case ITEM_QUALITY_EPIC: + case ITEM_QUALITY_LEGENDARY: + return randPropEntry->EpicPropertiesPoints[suffixFactor]; + } + + return 0; +} + + +uint32 ItemTemplate::GetStatValue(uint32 index, Player* owner /* = nullptr*/) const +{ + if (owner == nullptr) + return ItemStat[index].ItemStatValue; + + ScalingStatDistributionEntry const* ssd = ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(ScalingStatDistribution) : nullptr; + // req. check at equip, but allow use for extended range if range limit max level, set proper level + uint32 ssdLevel = owner->getLevel(); + + if (ssd && ssdLevel > ssd->MaxLevel) + ssdLevel = ssd->MaxLevel; + if (ssd && ssdLevel < ssd->MinLevel) + ssdLevel = ssd->MinLevel; + if (ssdLevel < 1) + ssdLevel = 1; + + ScalingStatValuesEntry const* ssv = ssd ? sScalingStatValuesStore.LookupEntry(ssdLevel) : nullptr; + + uint32 statBaseValue = 0; + if (ssd != nullptr && ssv != nullptr) + { + if (ssd->StatMod[index] < 0) + return 0; // What do we do ? + + statBaseValue = ssv->GetStatMultiplier(InventoryType) * ssd->Modifier[index] / 10000; + } + else + { + statBaseValue = ItemStat[index].ItemStatValue; + + uint32 itemLevel = ItemLevel; + uint32 maxItemLevel = owner->GetUInt32Value(UNIT_FIELD_MAXITEMLEVEL); + if (!maxItemLevel || maxItemLevel >= itemLevel) // TODO: This might work if >=. Check. + return statBaseValue; + + float minScaler = GetItemScalingModifier(ItemLevel, ItemQualities(Quality), ::InventoryType(InventoryType)); + float maxScaler = GetItemScalingModifier(maxItemLevel, ItemQualities(Quality), ::InventoryType(InventoryType)); + + if (maxScaler != 0.0f && minScaler != 0.0f) + { + float statAllocation = ItemStat[index].ItemStatAllocation * maxScaler * 0.0001f; + return std::ceil(statAllocation - ((maxScaler / minScaler) * ItemStat[index].ItemStatSocketCostMultiplier)); + } + } + + return statBaseValue; +} diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h index df2eaae1ac4..2cd7d25e635 100644 --- a/src/server/game/DataStores/DBCStructure.h +++ b/src/server/game/DataStores/DBCStructure.h @@ -1899,7 +1899,7 @@ struct ScalingStatDistributionEntry uint32 Id; // 0 int32 StatMod[10]; // 1-10 uint32 Modifier[10]; // 11-20 - //uint32 unk1; // 21 + uint32 MinLevel; // 21 uint32 MaxLevel; // 22 m_maxlevel }; diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h index 57dfe538d71..aa4cf3ae06f 100644 --- a/src/server/game/DataStores/DBCfmt.h +++ b/src/server/game/DataStores/DBCfmt.h @@ -131,7 +131,7 @@ char const ResearchBranchEntryfmt[] = "nsiisi"; char const ResearchFieldEntryfmt[] = "nsi"; char const ResearchProjectEntryfmt[] = "nssiiiisi"; char const ResearchSiteEntryfmt[] = "niiss"; -char const ScalingStatDistributionfmt[] = "niiiiiiiiiiiiiiiiiiiixi"; +char const ScalingStatDistributionfmt[] = "niiiiiiiiiiiiiiiiiiiiii"; char const ScalingStatValuesfmt[] = "iniiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"; char const SkillLinefmt[] = "nisxixi"; char const SkillLineAbilityfmt[] = "niiiixxiiiiiii"; diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index afd00ce6a0a..7b30b89aabe 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -613,8 +613,8 @@ struct _ItemStat { uint32 ItemStatType; int32 ItemStatValue; - int32 ItemStatUnk1; - int32 ItemStatUnk2; + int32 ItemStatAllocation; + int32 ItemStatSocketCostMultiplier; }; struct _Spell @@ -706,10 +706,6 @@ struct ItemTemplate uint32 CurrencySubstitutionCount; // extra fields, not part of db2 files - float DamageMin; - float DamageMax; - float DPS; - uint32 Armor; float SpellPPMRate; uint32 ScriptId; uint32 DisenchantID; @@ -744,6 +740,10 @@ struct ItemTemplate SubClass == ITEM_SUBCLASS_WEAPON_GUN || SubClass == ITEM_SUBCLASS_WEAPON_CROSSBOW; } + + uint32 GetStatValue(uint32 index, Player* owner = nullptr) const; + uint32 GetEffectiveArmor(Player * owner) const; + bool GetWeaponDamage(Player * owner, float& minValue, float& maxValue, float& dps) const; }; struct ItemLocale diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d77e1d08e44..3f69e731431 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7878,21 +7878,12 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i) { - uint32 statType = 0; - int32 val = 0; + int32 val = proto->GetStatValue(i, this); // If set ScalingStatDistribution need get stats and values from it - if (ssd && ssv) - { - if (ssd->StatMod[i] < 0) - continue; - statType = ssd->StatMod[i]; - val = (ssv->GetStatMultiplier(proto->InventoryType) * ssd->Modifier[i]) / 10000; - } - else - { - statType = proto->ItemStat[i].ItemStatType; - val = proto->ItemStat[i].ItemStatValue; - } + if (ssd && ssd->StatMod[i] < 0) + continue; + + uint32 statType = proto->ItemStat[i].ItemStatType; if (val == 0) continue; @@ -8065,11 +8056,9 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply ApplySpellPowerBonus(spellbonus, apply); // If set ScalingStatValue armor get it or use item armor - uint32 armor = proto->Armor; + uint32 armor = proto->GetEffectiveArmor(this); if (ssv && proto->Class == ITEM_CLASS_ARMOR) armor = ssv->GetArmor(proto->InventoryType, proto->SubClass - 1); - else if (armor && proto->ArmorDamageModifier) - armor -= uint32(proto->ArmorDamageModifier); if (armor) { @@ -8127,8 +8116,8 @@ void Player::_ApplyWeaponDamage(uint8 slot, ItemTemplate const* proto, ScalingSt attType = OFF_ATTACK; } - float minDamage = proto->DamageMin; - float maxDamage = proto->DamageMax; + float minDamage, maxDamage, dps; + proto->GetWeaponDamage(this, minDamage, maxDamage, dps); // If set dpsMod in ScalingStatValue use it for min (70% from average), max (130% from average) damage int32 extraDPS = 0; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 813c7d04c92..91d357382a3 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -2795,107 +2795,6 @@ void ObjectMgr::LoadItemLocales() TC_LOG_INFO("server.loading", ">> Loaded %u Item locale strings in %u ms", uint32(_itemLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime)); } -void FillItemDamageFields(float* minDamage, float* maxDamage, float* dps, uint32 itemLevel, uint32 itemClass, uint32 itemSubClass, uint32 quality, uint32 delay, float statScalingFactor, uint32 inventoryType, uint32 flags2) -{ - *minDamage = *maxDamage = *dps = 0.0f; - if (itemClass != ITEM_CLASS_WEAPON || quality > ITEM_QUALITY_ARTIFACT) - return; - - DBCStorage* store = nullptr; - // get the right store here - if (inventoryType > 0xD + 13) - return; - - switch (inventoryType) - { - case INVTYPE_AMMO: - store = &sItemDamageAmmoStore; - break; - case INVTYPE_2HWEAPON: - if (flags2 & ITEM_FLAG2_CASTER_WEAPON) - store = &sItemDamageTwoHandCasterStore; - else - store = &sItemDamageTwoHandStore; - break; - case INVTYPE_RANGED: - case INVTYPE_THROWN: - case INVTYPE_RANGEDRIGHT: - switch (itemSubClass) - { - case ITEM_SUBCLASS_WEAPON_WAND: - store = &sItemDamageWandStore; - break; - case ITEM_SUBCLASS_WEAPON_THROWN: - store = &sItemDamageThrownStore; - break; - case ITEM_SUBCLASS_WEAPON_BOW: - case ITEM_SUBCLASS_WEAPON_GUN: - case ITEM_SUBCLASS_WEAPON_CROSSBOW: - store = &sItemDamageRangedStore; - break; - default: - return; - } - break; - case INVTYPE_WEAPON: - case INVTYPE_WEAPONMAINHAND: - case INVTYPE_WEAPONOFFHAND: - if (flags2 & ITEM_FLAG2_CASTER_WEAPON) - store = &sItemDamageOneHandCasterStore; - else - store = &sItemDamageOneHandStore; - break; - default: - return; - } - - if (!store) - return; - - ItemDamageEntry const* damageInfo = store->LookupEntry(itemLevel); - if (!damageInfo) - return; - - *dps = damageInfo->DPS[quality]; - float avgDamage = *dps * delay * 0.001f; - *minDamage = (statScalingFactor * -0.5f + 1.0f) * avgDamage; - *maxDamage = floor(float(avgDamage* (statScalingFactor * 0.5f + 1.0f) + 0.5f)); -} - -uint32 FillItemArmor(uint32 itemlevel, uint32 itemClass, uint32 itemSubclass, uint32 quality, uint32 inventoryType) -{ - if (quality > ITEM_QUALITY_ARTIFACT) - return 0; - - // all items but shields - if (itemClass != ITEM_CLASS_ARMOR || itemSubclass != ITEM_SUBCLASS_ARMOR_SHIELD) - { - ItemArmorQualityEntry const* armorQuality = sItemArmorQualityStore.LookupEntry(itemlevel); - ItemArmorTotalEntry const* armorTotal = sItemArmorTotalStore.LookupEntry(itemlevel); - if (!armorQuality || !armorTotal) - return 0; - - if (inventoryType == INVTYPE_ROBE) - inventoryType = INVTYPE_CHEST; - - ArmorLocationEntry const* location = sArmorLocationStore.LookupEntry(inventoryType); - if (!location) - return 0; - - if (itemSubclass < ITEM_SUBCLASS_ARMOR_CLOTH || itemSubclass > ITEM_SUBCLASS_ARMOR_PLATE) - return 0; - - return uint32(armorQuality->Value[quality] * armorTotal->Value[itemSubclass - 1] * location->Value[itemSubclass - 1] + 0.5f); - } - - // shields - ItemArmorShieldEntry const* shield = sItemArmorShieldStore.LookupEntry(itemlevel); - if (!shield) - return 0; - - return uint32(shield->Value[quality] + 0.5f); -} - uint32 FillMaxDurability(uint32 itemClass, uint32 itemSubClass, uint32 inventoryType, uint32 quality, uint32 itemLevel) { if (itemClass != ITEM_CLASS_ARMOR && itemClass != ITEM_CLASS_WEAPON) @@ -3068,19 +2967,14 @@ void ObjectMgr::LoadItemTemplates() { itemTemplate.ItemStat[i].ItemStatType = sparse->ItemStatType[i]; itemTemplate.ItemStat[i].ItemStatValue = sparse->ItemStatValue[i]; - itemTemplate.ItemStat[i].ItemStatUnk1 = sparse->ItemStatUnk1[i]; - itemTemplate.ItemStat[i].ItemStatUnk2 = sparse->ItemStatUnk2[i]; + itemTemplate.ItemStat[i].ItemStatAllocation = sparse->ItemStatAllocation[i]; + itemTemplate.ItemStat[i].ItemStatSocketCostMultiplier = sparse->ItemStatSocketCostMultiplier[i]; } itemTemplate.ScalingStatDistribution = sparse->ScalingStatDistribution; // cache item damage - FillItemDamageFields(&itemTemplate.DamageMin, &itemTemplate.DamageMax, &itemTemplate.DPS, sparse->ItemLevel, - db2Data->Class, db2Data->SubClass, sparse->Quality, sparse->Delay, sparse->StatScalingFactor, - sparse->InventoryType, sparse->Flags2); - itemTemplate.DamageType = sparse->DamageType; - itemTemplate.Armor = FillItemArmor(sparse->ItemLevel, db2Data->Class, db2Data->SubClass, sparse->Quality, sparse->InventoryType); itemTemplate.Delay = sparse->Delay; itemTemplate.RangedModRange = sparse->RangedModRange; for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) @@ -3221,21 +3115,12 @@ void ObjectMgr::LoadItemTemplates() { itemTemplate.ItemStat[i].ItemStatType = uint32(fields[29 + i * 4 + 0].GetUInt8()); itemTemplate.ItemStat[i].ItemStatValue = int32(fields[29 + i * 4 + 1].GetInt16()); - itemTemplate.ItemStat[i].ItemStatUnk1 = fields[29 + i * 4 + 2].GetInt32(); - itemTemplate.ItemStat[i].ItemStatUnk2 = fields[29 + i * 4 + 3].GetInt32(); + itemTemplate.ItemStat[i].ItemStatAllocation = fields[29 + i * 4 + 2].GetInt32(); + itemTemplate.ItemStat[i].ItemStatSocketCostMultiplier = fields[29 + i * 4 + 3].GetInt32(); } itemTemplate.ScalingStatDistribution = uint32(fields[69].GetUInt16()); - - // cache item damage - FillItemDamageFields(&itemTemplate.DamageMin, &itemTemplate.DamageMax, &itemTemplate.DPS, itemTemplate.ItemLevel, - itemTemplate.Class, itemTemplate.SubClass, itemTemplate.Quality, fields[71].GetUInt16(), - fields[131].GetFloat(), itemTemplate.InventoryType, itemTemplate.Flags2); - itemTemplate.DamageType = fields[70].GetUInt8(); - itemTemplate.Armor = FillItemArmor(itemTemplate.ItemLevel, itemTemplate.Class, - itemTemplate.SubClass, itemTemplate.Quality, - itemTemplate.InventoryType); itemTemplate.Delay = fields[71].GetUInt16(); itemTemplate.RangedModRange = fields[72].GetFloat();