aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOvahlord <dreadkiller@gmx.de>2025-04-11 23:11:12 +0200
committerOvahlord <dreadkiller@gmx.de>2025-04-11 23:11:12 +0200
commitbc5698790af1728c6ef7f81d86aa3a00e3c97dd7 (patch)
tree1b6bddb14284c8ad612f161f0fa91bb6dc586f6a
parente3773a1fb60369016d28bad879b862a26390be21 (diff)
Core/Items: restore Heirloom stat scaling
-rw-r--r--sql/updates/hotfixes/cata_classic/2025_04_11_01_hotfixes.sql45
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.cpp6
-rw-r--r--src/server/game/DataStores/DB2LoadInfo.h8
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp13
-rw-r--r--src/server/game/DataStores/DB2Stores.h3
-rw-r--r--src/server/game/DataStores/DB2Structure.h6
-rw-r--r--src/server/game/Entities/Item/Item.cpp51
-rw-r--r--src/server/game/Entities/Item/Item.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp142
-rw-r--r--src/server/game/Entities/Player/Player.h7
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp4
11 files changed, 247 insertions, 40 deletions
diff --git a/sql/updates/hotfixes/cata_classic/2025_04_11_01_hotfixes.sql b/sql/updates/hotfixes/cata_classic/2025_04_11_01_hotfixes.sql
new file mode 100644
index 00000000000..5d9656dcedb
--- /dev/null
+++ b/sql/updates/hotfixes/cata_classic/2025_04_11_01_hotfixes.sql
@@ -0,0 +1,45 @@
+--
+-- Table structure for table `scaling_stat_distribution`
+--
+
+DROP TABLE IF EXISTS `scaling_stat_distribution`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!50503 SET character_set_client = utf8mb4 */;
+CREATE TABLE `scaling_stat_distribution` (
+ `ID` int unsigned NOT NULL DEFAULT '0',
+ `PlayerLevelToItemLevelCurveID` smallint unsigned NOT NULL DEFAULT '0',
+ `Minlevel` int NOT NULL DEFAULT '0',
+ `Maxlevel` int NOT NULL DEFAULT '0',
+ `Bonus1` int NOT NULL DEFAULT '0',
+ `Bonus2` int NOT NULL DEFAULT '0',
+ `Bonus3` int NOT NULL DEFAULT '0',
+ `Bonus4` int NOT NULL DEFAULT '0',
+ `Bonus5` int NOT NULL DEFAULT '0',
+ `Bonus6` int NOT NULL DEFAULT '0',
+ `Bonus7` int NOT NULL DEFAULT '0',
+ `Bonus8` int NOT NULL DEFAULT '0',
+ `Bonus9` int NOT NULL DEFAULT '0',
+ `Bonus10` int NOT NULL DEFAULT '0',
+ `StatID1` int NOT NULL DEFAULT '0',
+ `StatID2` int NOT NULL DEFAULT '0',
+ `StatID3` int NOT NULL DEFAULT '0',
+ `StatID4` int NOT NULL DEFAULT '0',
+ `StatID5` int NOT NULL DEFAULT '0',
+ `StatID6` int NOT NULL DEFAULT '0',
+ `StatID7` int NOT NULL DEFAULT '0',
+ `StatID8` int NOT NULL DEFAULT '0',
+ `StatID9` int NOT NULL DEFAULT '0',
+ `StatID10` int NOT NULL DEFAULT '0',
+ `VerifiedBuild` int NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`VerifiedBuild`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `scaling_stat_distribution`
+--
+
+LOCK TABLES `scaling_stat_distribution` WRITE;
+/*!40000 ALTER TABLE `scaling_stat_distribution` DISABLE KEYS */;
+/*!40000 ALTER TABLE `scaling_stat_distribution` ENABLE KEYS */;
+UNLOCK TABLES;
diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp
index a0e2bf66dd1..9fb645bc571 100644
--- a/src/server/database/Database/Implementation/HotfixDatabase.cpp
+++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp
@@ -1100,9 +1100,9 @@ void HotfixDatabaseConnection::DoPrepareStatements()
PREPARE_MAX_ID_STMT(HOTFIX_SEL_REWARD_PACK_X_ITEM, "SELECT MAX(ID) + 1 FROM reward_pack_x_item", CONNECTION_SYNCH);
// ScalingStatDistribution.db2
- PrepareStatement(HOTFIX_SEL_SCALING_STAT_DISTRIBUTION, "SELECT ID, StatID1, StatID2, StatID3, StatID4, StatID5, StatID6, StatID7, StatID8, "
- "StatID9, StatID10, Bonus1, Bonus2, Bonus3, Bonus4, Bonus5, Bonus6, Bonus7, Bonus8, Bonus9, Bonus10, Maxlevel FROM scaling_stat_distribution"
- " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
+ PrepareStatement(HOTFIX_SEL_SCALING_STAT_DISTRIBUTION, "SELECT ID, PlayerLevelToItemLevelCurveID, Minlevel, Maxlevel, Bonus1, Bonus2, Bonus3, "
+ "Bonus4, Bonus5, Bonus6, Bonus7, Bonus8, Bonus9, Bonus10, StatID1, StatID2, StatID3, StatID4, StatID5, StatID6, StatID7, StatID8, StatID9, "
+ "StatID10 FROM scaling_stat_distribution WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH);
PREPARE_MAX_ID_STMT(HOTFIX_SEL_SCALING_STAT_DISTRIBUTION, "SELECT MAX(ID) + 1 FROM scaling_stat_distribution", CONNECTION_SYNCH);
// ScalingStatValues.db2
diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h
index ae072a81863..b15c0407000 100644
--- a/src/server/game/DataStores/DB2LoadInfo.h
+++ b/src/server/game/DataStores/DB2LoadInfo.h
@@ -3713,9 +3713,12 @@ struct RewardPackXItemLoadInfo
struct ScalingStatDistributionLoadInfo
{
- static constexpr DB2FieldMeta Fields[22] =
+ static constexpr DB2FieldMeta Fields[24] =
{
{ false, FT_INT, "ID" },
+ { false, FT_SHORT, "PlayerLevelToItemLevelCurveID" },
+ { true, FT_INT, "Minlevel" },
+ { true, FT_INT, "Maxlevel" },
{ true, FT_INT, "StatID1" },
{ true, FT_INT, "StatID2" },
{ true, FT_INT, "StatID3" },
@@ -3736,10 +3739,9 @@ struct ScalingStatDistributionLoadInfo
{ true, FT_INT, "Bonus8" },
{ true, FT_INT, "Bonus9" },
{ true, FT_INT, "Bonus10" },
- { true, FT_INT, "Maxlevel" },
};
- static constexpr DB2LoadInfo Instance{ Fields, 22, &ScalingStatDistributionMeta::Instance, HOTFIX_SEL_SCALING_STAT_DISTRIBUTION };
+ static constexpr DB2LoadInfo Instance{ Fields, 24, &ScalingStatDistributionMeta::Instance, HOTFIX_SEL_SCALING_STAT_DISTRIBUTION };
};
struct ScalingStatValuesLoadInfo
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 705b5252718..7cd81ae8799 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -220,6 +220,8 @@ DB2Storage<RandPropPointsEntry> sRandPropPointsStore("RandPropPo
DB2Storage<RewardPackEntry> sRewardPackStore("RewardPack.db2", &RewardPackLoadInfo::Instance);
DB2Storage<RewardPackXCurrencyTypeEntry> sRewardPackXCurrencyTypeStore("RewardPackXCurrencyType.db2", &RewardPackXCurrencyTypeLoadInfo::Instance);
DB2Storage<RewardPackXItemEntry> sRewardPackXItemStore("RewardPackXItem.db2", &RewardPackXItemLoadInfo::Instance);
+DB2Storage<ScalingStatDistributionEntry> sScalingStatDistributionStore("ScalingStatDistribution.db2", &ScalingStatDistributionLoadInfo::Instance);
+DB2Storage<ScalingStatValuesEntry> sScalingStatValuesStore("ScalingStatValues.db2", &ScalingStatValuesLoadInfo::Instance);
DB2Storage<ScenarioEntry> sScenarioStore("Scenario.db2", &ScenarioLoadInfo::Instance);
DB2Storage<ScenarioStepEntry> sScenarioStepStore("ScenarioStep.db2", &ScenarioStepLoadInfo::Instance);
DB2Storage<SceneScriptEntry> sSceneScriptStore("SceneScript.db2", &SceneScriptLoadInfo::Instance);
@@ -417,6 +419,7 @@ namespace
WMOAreaTableLookupContainer _wmoAreaTableLookup;
std::array<std::unordered_map<int32, TalentTabEntry const*>, MAX_CLASSES> _talentTabsByIndex;
std::unordered_map<uint32, std::vector<uint32>> _primaryTalentTreeSpellsByTalentTab;
+ std::unordered_map<uint32, ScalingStatValuesEntry const*> _scalingStatValuesByCharacterLevel;
}
void LoadDB2(std::bitset<TOTAL_LOCALES>& availableDb2Locales, std::vector<std::string>& errlist, StorageMap& stores, DB2StorageBase* storage, std::string const& db2Path,
@@ -722,6 +725,8 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sRewardPackStore);
LOAD_DB2(sRewardPackXCurrencyTypeStore);
LOAD_DB2(sRewardPackXItemStore);
+ LOAD_DB2(sScalingStatDistributionStore);
+ LOAD_DB2(sScalingStatValuesStore);
LOAD_DB2(sScenarioStore);
LOAD_DB2(sScenarioStepStore);
LOAD_DB2(sSceneScriptStore);
@@ -1391,6 +1396,9 @@ void DB2Manager::IndexLoadedStores()
sOldContinentsNodesMask[field] |= submask;
}
+ for (ScalingStatValuesEntry const* ssd : sScalingStatValuesStore)
+ _scalingStatValuesByCharacterLevel[ssd->Charlevel] = ssd;
+
TC_LOG_INFO("server.loading", ">> Indexed DB2 data stores in {} ms", GetMSTimeDiffToNow(oldMSTime));
}
@@ -2853,3 +2861,8 @@ std::vector<ItemEffectEntry const*> const* DB2Manager::GetItemEffectsForItemId(u
{
return Trinity::Containers::MapGetValuePtr(_itemEffectsByItemId, itemId);
}
+
+ScalingStatValuesEntry const* DB2Manager::GetScalingStatValuesForLevel(uint32 level) const
+{
+ return Trinity::Containers::MapGetValuePtr(_scalingStatValuesByCharacterLevel, level);
+}
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index bfe0ea997b9..34803198506 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -169,6 +169,8 @@ TC_GAME_API extern DB2Storage<QuestSortEntry> sQuestSortSt
TC_GAME_API extern DB2Storage<QuestXPEntry> sQuestXPStore;
TC_GAME_API extern DB2Storage<RandPropPointsEntry> sRandPropPointsStore;
TC_GAME_API extern DB2Storage<RewardPackEntry> sRewardPackStore;
+TC_GAME_API extern DB2Storage<ScalingStatDistributionEntry> sScalingStatDistributionStore;
+TC_GAME_API extern DB2Storage<ScalingStatValuesEntry> sScalingStatValuesStore;
TC_GAME_API extern DB2Storage<ScenarioEntry> sScenarioStore;
TC_GAME_API extern DB2Storage<ScenarioStepEntry> sScenarioStepStore;
TC_GAME_API extern DB2Storage<SkillLineEntry> sSkillLineStore;
@@ -454,6 +456,7 @@ public:
WMOAreaTableEntry const* GetWMOAreaTable(int32 rootId, int32 adtId, int32 groupId) const;
std::unordered_set<uint32> const* GetPVPStatIDsForMap(uint32 mapId) const;
std::vector<ItemEffectEntry const*> const* GetItemEffectsForItemId(uint32 itemId) const;
+ ScalingStatValuesEntry const* GetScalingStatValuesForLevel(uint32 characterLevel) const;
private:
friend class DB2HotfixGeneratorBase;
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index 0b72240c29c..836163b7f3b 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -2888,9 +2888,11 @@ struct RewardPackXItemEntry
struct ScalingStatDistributionEntry
{
uint32 ID;
- std::array<int32, 10> StatID;
- std::array<int32, 10> Bonus;
+ uint16 PlayerLevelToItemLevelCurveID;
+ int32 Minlevel;
int32 Maxlevel;
+ std::array<int32, 10> Bonus;
+ std::array<int32, 10> StatID;
};
// structure for ScalingStatValues.db2
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp
index 0891ac897c5..dae8436cbe3 100644
--- a/src/server/game/Entities/Item/Item.cpp
+++ b/src/server/game/Entities/Item/Item.cpp
@@ -2033,10 +2033,59 @@ uint32 Item::GetItemLevel(ItemTemplate const* itemTemplate, BonusData const& bon
return std::min(std::max(itemLevel, uint32(MIN_ITEM_LEVEL)), uint32(MAX_ITEM_LEVEL));
}
-float Item::GetItemStatValue(uint32 index, Player const* /*owner*/) const
+float Item::GetItemStatValue(uint32 index, Player const* /*owner*/, ScalingStatDistributionEntry const* ssd /*= nullptr*/, ScalingStatValuesEntry const* ssv /*= nullptr*/) const
{
ASSERT(index < MAX_ITEM_PROTO_STATS);
+ // Stats from Heirlooms
+ if (ssd && ssv)
+ {
+ if (ssd->StatID[index] == -1)
+ return 0.0f;
+
+ int32 budget = 0;
+ switch (GetTemplate()->GetInventoryType())
+ {
+ case INVTYPE_HEAD:
+ case INVTYPE_CHEST:
+ case INVTYPE_LEGS:
+ case INVTYPE_2HWEAPON:
+ case INVTYPE_ROBE:
+ budget = ssv->BudgetPrimary;
+ break;
+ case INVTYPE_SHOULDERS:
+ case INVTYPE_WAIST:
+ case INVTYPE_FEET:
+ case INVTYPE_HANDS:
+ case INVTYPE_TRINKET:
+ budget = ssv->BudgetSecondary;
+ break;
+ case INVTYPE_NECK:
+ case INVTYPE_WRISTS:
+ case INVTYPE_FINGER:
+ case INVTYPE_SHIELD:
+ case INVTYPE_CLOAK:
+ case INVTYPE_HOLDABLE:
+ budget = ssv->BudgetTertiary;
+ break;
+ case INVTYPE_RANGED:
+ case INVTYPE_THROWN:
+ case INVTYPE_RANGEDRIGHT:
+ case INVTYPE_RELIC:
+ budget = ssv->BudgetSub;
+ break;
+ case INVTYPE_WEAPON:
+ case INVTYPE_WEAPONMAINHAND:
+ case INVTYPE_WEAPONOFFHAND:
+ budget = ssv->BudgetTrivial;
+ break;
+ default:
+ break;
+ }
+
+ return static_cast<float>(budget * ssd->Bonus[index] / 10000.0f);
+ }
+
return static_cast<float>(_bonusData.ItemStatAmount[index]);
}
diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h
index 9066b46ba45..7b3f18c4dff 100644
--- a/src/server/game/Entities/Item/Item.h
+++ b/src/server/game/Entities/Item/Item.h
@@ -287,7 +287,7 @@ class TC_GAME_API Item : public Object
uint32 minItemLevel, uint32 minItemLevelCutoff, uint32 maxItemLevel, bool pvpBonus);
int32 GetRequiredLevel() const;
int32 GetItemStatType(uint32 index) const { ASSERT(index < MAX_ITEM_PROTO_STATS); return _bonusData.ItemStatType[index]; }
- float GetItemStatValue(uint32 index, Player const* owner) const;
+ float GetItemStatValue(uint32 index, Player const* owner, ScalingStatDistributionEntry const* ssd = nullptr, ScalingStatValuesEntry const* ssv = nullptr) const;
SocketColor GetSocketColor(uint32 index) const { ASSERT(index < MAX_ITEM_PROTO_SOCKETS); return SocketColor(_bonusData.SocketColor[index]); }
uint32 GetAppearanceModId() const { return m_itemData->ItemAppearanceModID; }
void SetAppearanceModId(uint32 appearanceModId) { SetUpdateFieldValue(m_values.ModifyValue(&Item::m_itemData).ModifyValue(&UF::ItemData::ItemAppearanceModID), appearanceModId); }
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 1d596c017c7..8dc39cdd221 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -7393,7 +7393,7 @@ void Player::DuelComplete(DuelCompleteType type)
//---------------------------------------------------------//
-void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras /*= true*/)
+void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras /*= true*/, bool updateHeirloomStats /*= false*/)
{
if (slot >= INVENTORY_SLOT_BAG_END || !item)
return;
@@ -7411,7 +7411,7 @@ void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemA
if (item->GetSocketColor(0)) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
CorrectMetaGemEnchants(slot, apply);
- _ApplyItemBonuses(item, slot, apply);
+ _ApplyItemBonuses(item, slot, apply, updateHeirloomStats);
ApplyItemEquipSpell(item, apply);
if (updateItemAuras)
{
@@ -7427,64 +7427,156 @@ void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemA
TC_LOG_DEBUG("entities.player.items", "Player::_ApplyItemMods: completed");
}
-void Player::_ApplyItemBonuses(Item* item, uint8 slot, bool apply)
+void Player::_ApplyItemBonuses(Item* item, uint8 slot, bool apply, bool onlyLevelScaling /*= false*/)
{
ItemTemplate const* proto = item->GetTemplate();
if (slot >= INVENTORY_SLOT_BAG_END || !proto)
return;
+ ScalingStatDistributionEntry const* ssd = sScalingStatDistributionStore.LookupEntry(proto->GetScalingStatDistributionID());
+ ScalingStatValuesEntry const* ssv = ssd ? sDB2Manager.GetScalingStatValuesForLevel(std::clamp<uint32>(GetLevel(), ssd->Minlevel, ssd->Maxlevel)) : nullptr;
+ if (onlyLevelScaling && (!ssd || !ssv))
+ return;
+
for (uint8 i = 0; i < MAX_ITEM_PROTO_STATS; ++i)
{
int32 statType = item->GetItemStatType(i);
- if (statType == -1)
+ if ((statType == -1 && !ssd) || (statType == -1 && ssd && ssd->StatID[i] == -1))
continue;
- float val = item->GetItemStatValue(i, this);
+ if (statType == -1 && ssd)
+ statType = ssd->StatID[i];
+
+ float val = item->GetItemStatValue(i, this, ssd, ssv);
if (val == 0)
continue;
ApplyItemModModifier(static_cast<ItemModType>(statType), val, apply);
}
+ // Spellpower bonus from Heirlooms
+ if (ssd && proto->HasFlag(ItemFlags2::ITEM_FLAG2_CASTER_WEAPON))
+ if (int32 spellPowerBonus = ssv->SpellPower)
+ ApplySpellPowerBonus(spellPowerBonus, apply);
+
+ // Armor bonus from Heirlooms
+ int32 overrideArmor = 0;
+ if (ssd && proto->GetClass() == ITEM_CLASS_ARMOR)
+ {
+ switch (proto->GetInventoryType())
+ {
+ case INVTYPE_HEAD:
+ overrideArmor = ssv->HeadArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_SHOULDERS:
+ overrideArmor = ssv->ShoulderArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_CHEST:
+ case INVTYPE_ROBE:
+ overrideArmor = ssv->ChestArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_LEGS:
+ overrideArmor = ssv->LegsArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_FEET:
+ overrideArmor = ssv->FeetArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_WAIST:
+ overrideArmor = ssv->WaistArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_HANDS:
+ overrideArmor = ssv->HandsArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_WRISTS:
+ overrideArmor = ssv->WristsArmor[proto->GetSubClass() - 1];
+ break;
+ case INVTYPE_CLOAK:
+ overrideArmor = ssv->ClothCloakArmor;
+ break;
+ default:
+ break;
+ }
+ }
+
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
- if (int16 resistance = proto->GetResistance(SpellSchools(i)))
+ {
+ int16 resistance = proto->GetResistance(SpellSchools(i));
+ if (i == SPELL_SCHOOL_NORMAL && overrideArmor)
+ resistance = overrideArmor;
+
+ if (resistance)
+ {
+ if (i == SPELL_SCHOOL_NORMAL && overrideArmor)
+ resistance = overrideArmor;
+
HandleStatFlatModifier(UnitMods(UNIT_MOD_ARMOR + i), BASE_VALUE, float(resistance), apply);
+ }
+ }
WeaponAttackType attType = Player::GetAttackBySlot(slot, proto->GetInventoryType());
if (attType != MAX_ATTACK)
- _ApplyWeaponDamage(slot, item, apply);
+ _ApplyWeaponDamage(slot, item, ssv, apply);
}
// @TODO: Ovahlord ScalingStat db2s
-void Player::_ApplyWeaponDamage(uint8 slot, Item* item, bool apply)
+void Player::_ApplyWeaponDamage(uint8 slot, Item* item, ScalingStatValuesEntry const* ssv, bool apply)
{
ItemTemplate const* proto = item->GetTemplate();
WeaponAttackType attType = Player::GetAttackBySlot(slot, proto->GetInventoryType());
if (!IsInFeralForm() && apply && !CanUseAttackType(attType))
return;
- // ScalingStatDistributionEntry const* ssd = sScalingStatDistributionStore.LookupEntry(proto->GetScalingStatDistributionID());
- // ScalingStatValuesEntry const* ssv = (ssd && proto->GetScalingStatValue() != 0) ? sDB2Manager.GetScalingStatValuesForLevel(std::clamp<uint32>(GetLevel(), ssd->MinLevel, ssd->MaxLevel)) : nullptr;
-
float damage = 0.0f;
//for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i)
{
int32 minDamage = proto->GetMinDamage(0);
int32 maxDamage = proto->GetMaxDamage(0);
- // If set dpsMod in ScalingStatValue use it for min (70% from average), max (130% from average) damage
- // if (ssv)
- // {
- // int32 extraDPS = ssv->getDPSMod(proto->GetScalingStatValue());
- // if (extraDPS)
- // {
- // float average = extraDPS * proto->GetDelay() / 1000.0f;
- // float mod = ssv->isTwoHand(proto->GetScalingStatValue()) ? 0.2f : 0.3f;
- //
- // minDamage = (1.0f - mod) * average;
- // maxDamage = (1.0f + mod) * average;
- // }
- // }
+ // Weapon damage from Heirlooms
+ if (ssv)
+ {
+ float multiplier = 0.0f;
+ int32 dps = 0;
+ switch (proto->GetSubClass())
+ {
+ case ITEM_SUBCLASS_WEAPON_AXE2:
+ case ITEM_SUBCLASS_WEAPON_MACE2:
+ case ITEM_SUBCLASS_WEAPON_POLEARM:
+ case ITEM_SUBCLASS_WEAPON_SWORD2:
+ case ITEM_SUBCLASS_WEAPON_STAFF:
+ case ITEM_SUBCLASS_WEAPON_FISHING_POLE:
+ dps = proto->HasFlag(ItemFlags2::ITEM_FLAG2_CASTER_WEAPON) ? ssv->SpellcasterDPS2H : ssv->WeaponDPS2H;
+ multiplier = 0.2f;
+ break;
+ case ITEM_SUBCLASS_WEAPON_BOW:
+ case ITEM_SUBCLASS_WEAPON_GUN:
+ case ITEM_SUBCLASS_WEAPON_CROSSBOW:
+ dps = ssv->RangedDPS;
+ multiplier = 0.3f;
+ break;
+ case ITEM_SUBCLASS_WEAPON_AXE:
+ case ITEM_SUBCLASS_WEAPON_MACE:
+ case ITEM_SUBCLASS_WEAPON_SWORD:
+ case ITEM_SUBCLASS_WEAPON_DAGGER:
+ case ITEM_SUBCLASS_WEAPON_THROWN:
+ dps = proto->HasFlag(ItemFlags2::ITEM_FLAG2_CASTER_WEAPON) ? ssv->SpellcasterDPS1H : ssv->WeaponDPS1H;
+ multiplier = 0.3f;
+ break;
+ case ITEM_SUBCLASS_WEAPON_WAND:
+ dps = ssv->WandDPS;
+ multiplier = 0.3f;
+ break;
+ default:
+ break;
+ }
+
+ if (dps > 0)
+ {
+ float average = dps * proto->GetDelay() / 1000.0f;
+ minDamage = (1.0f - multiplier) * average;
+ maxDamage = (1.0f + multiplier) * average;
+ }
+ }
if (minDamage > 0)
{
@@ -8109,7 +8201,7 @@ void Player::_ApplyAllLevelScaleItemMods(bool apply)
if (!CanUseAttackType(Player::GetAttackBySlot(i, m_items[i]->GetTemplate()->GetInventoryType())))
continue;
- _ApplyItemMods(m_items[i], i, apply);
+ _ApplyItemMods(m_items[i], i, apply, true);
// Update item sets for heirlooms
if (sDB2Manager.GetHeirloomByItemId(m_items[i]->GetEntry()) && m_items[i]->GetTemplate()->GetItemSet())
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index b5033940eba..698d8eff36c 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -54,6 +54,7 @@ struct Mail;
struct MapEntry;
struct QuestPackageItemEntry;
struct RewardPackEntry;
+struct ScalingStatValuesEntry;
struct SkillRaceClassInfoEntry;
struct SpellCastRequest;
struct TalentEntry;
@@ -2305,12 +2306,12 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
bool CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEffect const* aurEff) const override;
- void _ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras = true);
+ void _ApplyItemMods(Item* item, uint8 slot, bool apply, bool updateItemAuras = true, bool updateHeirloomStats = false);
void _RemoveAllItemMods();
void _ApplyAllItemMods();
void _ApplyAllLevelScaleItemMods(bool apply);
- void _ApplyItemBonuses(Item* item, uint8 slot, bool apply);
- void _ApplyWeaponDamage(uint8 slot, Item* item, bool apply);
+ void _ApplyItemBonuses(Item* item, uint8 slot, bool apply, bool onlyLevelScaling = false);
+ void _ApplyWeaponDamage(uint8 slot, Item* item, ScalingStatValuesEntry const* ssv, bool apply);
bool EnchantmentFitsRequirements(uint32 enchantmentcondition, int8 slot) const;
void ToggleMetaGemsActive(uint8 exceptslot, bool apply);
void CorrectMetaGemEnchants(uint8 slot, bool apply);
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index 0e636e14218..76f38ad7632 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -2021,7 +2021,7 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo
if (!target->CanUseAttackType(BASE_ATTACK))
{
if (Item* pItem = target->ToPlayer()->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND))
- target->ToPlayer()->_ApplyWeaponDamage(EQUIPMENT_SLOT_MAINHAND, pItem, apply);
+ target->ToPlayer()->_ApplyWeaponDamage(EQUIPMENT_SLOT_MAINHAND, pItem, nullptr, apply);
}
}
@@ -2461,7 +2461,7 @@ void AuraEffect::HandleAuraModDisarm(AuraApplication const* aurApp, uint8 mode,
player->ApplyItemDependentAuras(item, !apply);
if (attackType != MAX_ATTACK)
{
- player->_ApplyWeaponDamage(slot, item, !apply);
+ player->_ApplyWeaponDamage(slot, item, nullptr, !apply);
if (!apply) // apply case already handled on item dependent aura removal (if any)
player->UpdateWeaponDependentAuras(attackType);
}