Core/Items: reworked item weapon damage and armor calculations based on what client does internally

This commit is contained in:
Warpten
2019-12-08 01:44:54 +01:00
committed by Ovahlord
parent 5cedd2dcc3
commit 3b7e135f52
8 changed files with 275 additions and 150 deletions

View File

@@ -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

View File

@@ -87,10 +87,10 @@ void DB2Utilities::WriteItemSparseDbReply(DB2Storage<ItemSparseEntry> 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);

View File

@@ -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<ItemDamageEntry>* 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;
}

View File

@@ -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
};

View File

@@ -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";

View File

@@ -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

View File

@@ -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;

View File

@@ -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<ItemDamageEntry>* 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();