From 3f3f63f90ea4d441d5075ab14171e2a525474f85 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Fri, 1 Aug 2025 10:06:55 -0400 Subject: [PATCH] Updates to enemy scaling mechanics for heroic, raids --- conf/mod-mythic-plus.conf.dist | 22 ++- src/MpDataStore.cpp | 18 +- src/MpDataStore.h | 8 +- src/MythicPlus.cpp | 266 +++++++++++++++++++++++---- src/MythicPlus.h | 9 +- src/Scripts/UnitScript.cpp | 320 +++++++++++++++++++++++++++++++-- src/Scripts/WorldScript.cpp | 9 + 7 files changed, 587 insertions(+), 65 deletions(-) diff --git a/conf/mod-mythic-plus.conf.dist b/conf/mod-mythic-plus.conf.dist index 43e1f2e..c346863 100644 --- a/conf/mod-mythic-plus.conf.dist +++ b/conf/mod-mythic-plus.conf.dist @@ -6,8 +6,8 @@ # ########################################################## -Appender.MythicPlusLog=2,5,0,mod-mythic-plus.log,w -Appender.MythicPlusConsole=1,5,0,"1 9 3 6 5 8" +Appender.MythicPlusLog=2,4,0,mod-mythic-plus.log,w +# Appender.MythicPlusConsole=1,5,0,"1 9 3 6 5 8" Logger.module.MythicPlus=5,MythicPlusLog MythicPlusConsole ########################################################## @@ -36,11 +36,13 @@ MythicPlus.EnableDeathLimits = 1 ########################################################## # -# Mythic+ Stat Modifiers By Difficuty +# Mythic+ Global Stat Modifiers By Difficulty # - These values are used to adjust the difficulty of enemies and bosses base # stats based on the difficulty of the dungeon. # - Bosses will only receive their multiplier not the Dungeon multiplier to prevent multiplicative scaling. +# - These values are used to scale the difficulty of all dungeons across new difficulty modes. # +# To scale individual instances you should use the mp_scale_factors table per instance to increase/reduce Modifiers ########################################################## MythicPlus.Mythic.DungeonHealth = 1.25 @@ -103,7 +105,7 @@ MythicPlus.Ascendant.DeathAllowance = 15 # - These values are used to set the offset to where items per difficulty will map to. # - Item drops in this mode use the original item entry + on offset to determine the new item entry reward. # - This enables a fast lookup at loot drop time as it is just an addition operation. -# - If using the base geneated sql the settings below will map to it. If the offset is incorrect, +# - If using the base generated sql the settings below will map to it. If the offset is incorrect, # the item will not be found and the original low level item will be rewarded. # ########################################################## @@ -112,6 +114,18 @@ MythicPlus.Mythic.ItemOffset = 20000000 MythicPlus.Legendary.ItemOffset = 21000000 MythicPlus.Ascendant.ItemOffset = 22000000 +########################################################## +# Diminishing Returns +# - These values are used to set the diminishing returns for the different difficulties. +# - The diminishing returns are used to limit the amount of damage that can be done to a target. +# - The diminishing returns are based on the difficulty of the dungeon and the amount of damage that has been done to the target. +########################################################## + +MythicPlus.DiminishingExponent = 0.98 +MythicPlus.DiminishingThreshold.Mythic = 10000 +MythicPlus.DiminishingThreshold.Legendary = 20000 +MythicPlus.DiminishingThreshold.Ascendant = 40000 + ############## # Scaling Adjusters ############# diff --git a/src/MpDataStore.cpp b/src/MpDataStore.cpp index 80185ea..46b1f5a 100644 --- a/src/MpDataStore.cpp +++ b/src/MpDataStore.cpp @@ -223,9 +223,9 @@ MpScaleFactor MpDataStore::GetScaleFactor(int32 mapId, int32 difficulty) const { // Just send back untouched bonus database will override. return MpScaleFactor{ .meleeBonus = 1.0f, - .healthBonus = 1.0f, .spellBonus = 1.0f, - .healBonus = 1.0f + .healBonus = 1.0f, + .healthBonus = 1.0f }; } @@ -302,9 +302,9 @@ int32 MpDataStore::LoadScaleFactors() { MpScaleFactor scaleFactor = { .meleeBonus = meleeBonus, - .healthBonus = healthBonus, .spellBonus = spellBonus, - .healBonus = healBonus + .healBonus = healBonus, + .healthBonus = healthBonus }; _scaleFactors->emplace(GetScaleFactorKey(mapId, difficulty), scaleFactor); @@ -321,11 +321,11 @@ void MpDataStore::LoadPlayerHealthAvg() { SELECT Level, ROUND(CASE - WHEN Level BETWEEN 1 AND 30 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.0 - WHEN Level BETWEEN 31 AND 50 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.3 - WHEN Level BETWEEN 51 AND 59 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.6 - WHEN Level BETWEEN 60 AND 69 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.0 - WHEN Level BETWEEN 70 AND 79 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.5 + WHEN Level BETWEEN 1 AND 30 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.5 + WHEN Level BETWEEN 31 AND 50 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.7 + WHEN Level BETWEEN 51 AND 59 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.0 + WHEN Level BETWEEN 60 AND 69 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.3 + WHEN Level BETWEEN 70 AND 79 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.6 WHEN Level BETWEEN 80 AND 84 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 3.0 WHEN Level >= 85 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 4.0 ELSE ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) diff --git a/src/MpDataStore.h b/src/MpDataStore.h index 01874ef..3d5b966 100644 --- a/src/MpDataStore.h +++ b/src/MpDataStore.h @@ -225,6 +225,7 @@ struct MpCreatureData // Original information about the creature that was altered. uint8 originalLevel; + uint32 originalInstanceHealth; // Health the creature would have in instance before Mythic+ scaling CreatureBaseStats const* originalStats; MpDifficulty difficulty; @@ -234,7 +235,7 @@ struct MpCreatureData std::vector affixes; MpCreatureData(Creature* creature) - : creature(creature), scaled(false) + : creature(creature), scaled(false), originalInstanceHealth(0) { if(creature) { originalLevel = creature->GetLevel(); @@ -242,6 +243,9 @@ struct MpCreatureData originalLevel, creature->GetCreatureTemplate()->unit_class ); + + originalInstanceHealth = creature->GetMaxHealth(); + } auras.reserve(3); @@ -264,7 +268,7 @@ struct MpCreatureData std::string origStatsStr; if(originalStats) { - uint32 health = *originalStats->BaseHealth; + uint32 health = originalInstanceHealth; uint32 mana = originalStats->BaseMana; uint32 armor = originalStats->BaseArmor; uint32 ap = originalStats->AttackPower; diff --git a/src/MythicPlus.cpp b/src/MythicPlus.cpp index 28d36c5..ca6e757 100644 --- a/src/MythicPlus.cpp +++ b/src/MythicPlus.cpp @@ -260,10 +260,16 @@ void MythicPlus::RemoveCreature(Creature* creature) void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* multipliers, MpDifficulty difficulty) { - uint32 origLevel = creature->GetLevel(); CreatureTemplate const* cInfo = creature->GetCreatureTemplate(); uint32 mapId = creature->GetMapId(); + // get the map difficulty from the map instance to see if it is a heroic or normal set instance + InstanceMap *instanceMap = creature->GetMap()->ToInstanceMap(); + if (!instanceMap) { + MpLogger::error("Invalid instance map ScaleCreature()"); + return; + } + creature->SetLevel(level); CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats( level, @@ -302,9 +308,22 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m // Handle new melee/range scaling with simple formula (for simplicity range will just be 80% of melee bonus) float meleeMultiplier = sMpDataStore->GetMeleeScaleFactor(creature->GetMapId(), instanceData->difficulty); + // Since Heroic Scaling can get out of hand. Reduce the instance multiplier by way too much 10% + if(instanceMap->IsHeroic() || instanceMap->Is25ManRaid()) { + // if the enemy is a boss reduce it by less + meleeMultiplier *= 0.9f; + } + // Scale up the attack power based on the instance set in the database difficulty table. uint32 ap = std::ceil(stats->AttackPower * meleeMultiplier); - uint32 rangeAp = std::ceil(stats->RangedAttackPower * meleeMultiplier * 0.5f); + uint32 rangeAp = std::ceil(stats->RangedAttackPower * meleeMultiplier * 0.4f); + + // Additionally need to add in a decrease in attack power for normal non elite enemies + if (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { + // Reduced scaling for elite/boss spells to prevent them from hitting too hard + ap *= 0.5f; + rangeAp *= 0.5f; + } MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID()); if(creatureData) { @@ -320,8 +339,8 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m creature->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING], 0); creature->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 1.5f, 0); - creature->SetBaseWeaponDamage(RANGED_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 0.6f, 0); - creature->SetBaseWeaponDamage(RANGED_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 1.0f, 0); + creature->SetBaseWeaponDamage(RANGED_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 0.5f, 0); + creature->SetBaseWeaponDamage(RANGED_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 0.8f, 0); // Update all stats to apply the new damage values creature->UpdateAllStats(); @@ -330,6 +349,10 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m uint32 armor = uint32(std::ceil(stats->BaseArmor * multipliers->armor * cInfo->ModArmor)); creature->SetArmor(armor); + float updatedAp = creature->GetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE); + float updatedRangeAp = creature->GetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE); + + MpLogger::debug("Updated Attack Powers: {} {}", updatedAp, updatedRangeAp); } int32 MythicPlus::CalculateSpellDamage(uint32 baseDamage, int originalLevel, int targetLevel) { @@ -338,11 +361,46 @@ int32 MythicPlus::CalculateSpellDamage(uint32 baseDamage, int originalLevel, int // Using a % of expected damage of the average player pool creates a better consistent experience when scaling spells float percentDamage = baseDamage / origHpPool; + + // If the percentage damage is less than 2% cap it at 2% to prevent spells from being too powerful + if(percentDamage < 0.02f) { + percentDamage = 0.02f; + } + + MpLogger::debug("OrigHpPool: {} TargetHpPool: {} Percent Damage: {}", origHpPool, targetHpPool, percentDamage); + MpLogger::debug("Original Damage: {} Scaled Damage: {}", baseDamage, static_cast(std::ceil(percentDamage * targetHpPool))); int32 scaledDamage = static_cast(std::ceil(percentDamage * targetHpPool)); return scaledDamage; } +int32 MythicPlus::CalculateHealScaling(uint32 baseHeal, uint32 originalTargetHealth, uint32 targetMaxHealth) { + if (originalTargetHealth == 0) { + MpLogger::debug("Original target health is 0, returning base heal: {}", baseHeal); + return baseHeal; + } + + // Calculate the percentage of the original heal relative to original creature health + float percentHeal = static_cast(baseHeal) / static_cast(originalTargetHealth); + + if (percentHeal < 0.01f) { + percentHeal = 0.01f; + } + + // Cap the percentage at 100% aka full heal of current max health of caster + if (percentHeal > 1.0f) { + percentHeal = 1.0f; + } + + // Scale the heal based on the current creature's max health + int32 scaledHeal = static_cast(std::ceil(percentHeal * targetMaxHealth)); + + MpLogger::debug("HEALING: >>> OrigHealth: {} CurrentMaxHealth: {} Percent Heal: {} Original Heal: {} Scaled Heal: {}", + originalTargetHealth, targetMaxHealth, percentHeal, baseHeal, scaledHeal); + + return scaledHeal; +} + int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, MpCreatureData* creatureData, Creature* creature, Unit* target, float damageMultiplier) { if (!spellInfo) { @@ -358,6 +416,8 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M float scaleFactor = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty); + MpLogger::debug("DAMAGE SPELL: >> ScaleFactor: {} DamageMultiplier: {}", scaleFactor, damageMultiplier); + // calculate the global modifier x instance modifier float totalModifier = damageMultiplier * scaleFactor; @@ -382,6 +442,7 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M // Handle totems and summons - scale based on owner's details because they will not have creature data if(creature->IsTotem() || creature->IsSummon()) { + Unit* owner = creature->GetOwner(); if(owner && owner->IsCreature()) { Creature* ownerCreature = owner->ToCreature(); @@ -389,16 +450,17 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M // Look up the owner creature's original level from MpDataStore MpCreatureData* ownerCreatureData = sMpDataStore->GetCreatureData(ownerCreature->GetGUID()); if (ownerCreatureData) { + MpLogger::debug("DAMAGE SPELL: >> Creature is a totem or summon Creature Name {} and owner {} owner original level {} owner level {}", creature->GetName(), ownerCreature->GetName(), ownerCreatureData->originalLevel, ownerCreature->GetLevel()); int32 ownerOriginalLevel = ownerCreatureData->originalLevel; if (ownerCreature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { - totalModifier = totalModifier * 0.5f; + totalModifier = totalModifier * 0.75f; } newDamage = CalculateSpellDamage(damage, ownerOriginalLevel, ownerCreature->GetLevel()); } else { // Fallback if no creature data found - use current level if(ownerCreature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { - totalModifier = totalModifier * 0.5f; + totalModifier = totalModifier * 0.75f; } newDamage = CalculateSpellDamage(damage, ownerCreature->GetLevel(), ownerCreature->GetLevel()); MpLogger::debug("No creature data found for owner {}, using current level for scaling", ownerCreature->GetGUID().ToString()); @@ -413,54 +475,179 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M newDamage = CalculateSpellDamage(damage, creatureData->originalLevel, creature->GetLevel()); if (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { // Reduced scaling for elite/boss spells to prevent them from hitting too hard - totalModifier = totalModifier * 0.5f; + totalModifier = totalModifier * 0.85f; } } + // Calculate the additional damage from scaling (scaled damage - original damage) + int32 additionalDamage = newDamage - damage; + // Apply the scaling modifier to the additional damage only + int32 scaledAdditionalDamage = additionalDamage * totalModifier; - MpLogger::debug(" >>> Spell {} damage scaled from for spell New Damage: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], totalDamage, scalingFactor, damageMultiplier); + // Use the diminishing return values from the configuration + uint32 threshold = sMythicPlus->diminishingThresholds[instanceData->difficulty]; + float diminishingExponent = sMythicPlus->diminishingExponent; - // Apply scaling factor and the set multiplier from the instance data - totalDamage = int32(totalDamage * scalingFactor * damageMultiplier); + // Apply diminishing returns only to the additional scaled damage if it exceeds threshold + if (static_cast(scaledAdditionalDamage) > threshold) { + // Calculate the diminished excess additional damage + float excess = scaledAdditionalDamage - threshold; + float diminishedExcess = pow(excess, diminishingExponent); + scaledAdditionalDamage = threshold + diminishedExcess; - // MpLogger::debug("Spell {} damage scaled from for spell New Damage: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], totalDamage, scalingFactor, damageMultiplier); - return totalDamage; + MpLogger::debug("DAMAGE SPELL: >> Above Diminishing Threshold for Spell {} - Original: {}, Additional: {}, Diminished Additional: {}, Final: {}", + spellInfo->SpellName[0], damage, additionalDamage * totalModifier, scaledAdditionalDamage, damage + scaledAdditionalDamage); + } else { + MpLogger::debug("DAMAGE SPELL: >> Below Diminishing Threshold for Spell {} - Original: {}, Additional: {}, Final: {}", + spellInfo->SpellName[0], damage, scaledAdditionalDamage, damage + scaledAdditionalDamage); + } + + // If this is a heroic instance the additional spell damage should be increased by 50% + InstanceMap* instanceMap = creature->GetMap()->ToInstanceMap(); + if (instanceMap && (instanceMap->IsHeroic() || instanceMap->Is25ManRaid())) { + scaledAdditionalDamage = scaledAdditionalDamage * 1.5f; + } + + // Return original damage + scaled additional damage (with potential diminishing returns) + return damage + scaledAdditionalDamage; } -int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCreatureData* creatureData, Creature* creature, Creature* /* target */, float healMultiplier) +int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCreatureData* creatureData, Creature* creature, Creature* target, float healMultiplier) { if (!spellInfo) { MpLogger::error("Invalid spell info ScaleHealSpell()"); - return 0; + return heal; } - if(!creatureData) { - MpLogger::error("Invalid creature data ScaleHealSpell()"); - return 0; + MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId()); + if (!instanceData) { + MpLogger::debug("No instance data found for heal scaling, using original heal"); + return heal; } + float scaleFactor = sMpDataStore->GetHealScaleFactor(creature->GetMapId(), instanceData->difficulty); + + MpLogger::debug("HEALING: >>> HealScaleFactor: {} HealMultiplier: {}", scaleFactor, healMultiplier); + + // calculate the global modifier x instance modifier + float totalModifier = healMultiplier * scaleFactor; + + // If for some reason there is not a creature, just use the global modifier x instance modifier if(!creature) { MpLogger::error("Invalid creature ScaleHealSpell()"); - return 0; + return heal * totalModifier; } - auto effects = spellInfo->GetEffects(); - for (uint8 i = 0; i < effects.size(); ++i) - { - MpLogger::debug(" >>> Spell {} effect {} value: {} by creature {}", spellInfo->SpellName[i], effects[i].Effect, heal, creature->GetName()); + // Use the already calculated heal as the base for scaling + int32 newHeal = heal; + + // Handle Summoned unit modifiers + if(!creatureData) { + + // handle if bot pets if NPCBot is installed. + #ifdef NPCBOT + if(creature->IsNPCBotOrPet()) { + return heal; + } + #endif + + MpLogger::debug("HEALING: >>> Scaling heal to target: {} with spell: {}", target->GetName(), spellInfo->SpellName[0]); + + // Handle totems and summons - scale based on owner's details because they will not have creature data + if(creature->IsTotem() || creature->IsSummon()) { + Unit* owner = creature->GetOwner(); + if(owner && owner->IsCreature()) { + Creature* ownerCreature = owner->ToCreature(); + + // Look up the owner creature's original level from MpDataStore + MpCreatureData* ownerCreatureData = sMpDataStore->GetCreatureData(ownerCreature->GetGUID()); + if (ownerCreatureData) { + if (ownerCreature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { + totalModifier = totalModifier * 0.7f; // Less reduction for heals than damage + } + // Scale heal based on target's health, not caster's health + if (target) { + MpCreatureData* targetCreatureData = sMpDataStore->GetCreatureData(target->GetGUID()); + uint32 targetOriginalHealth = targetCreatureData && targetCreatureData->originalInstanceHealth > 0 ? + targetCreatureData->originalInstanceHealth : target->GetMaxHealth(); + MpLogger::debug("HEALING: >>> Scaling heal to target: {} Original Instance Health: {} New Health: {}", target->GetName(), targetOriginalHealth, target->GetMaxHealth()); + newHeal = CalculateHealScaling(heal, targetOriginalHealth, target->GetMaxHealth()); + } else { + newHeal = heal; + } + } else { + // Fallback if no creature data found - use current level + if(ownerCreature->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL) { + totalModifier = totalModifier * 0.7f; // Less reduction for heals than damage + } + // Scale heal based on target's health, not caster's health + if (target) { + MpCreatureData* targetCreatureData = sMpDataStore->GetCreatureData(target->GetGUID()); + uint32 targetOriginalHealth = targetCreatureData && targetCreatureData->originalInstanceHealth > 0 ? + targetCreatureData->originalInstanceHealth : target->GetMaxHealth(); + + MpLogger::debug("HEALING: >>> Scaling heal to target: {} Original Instance Health: {} New Health: {}", target->GetName(), targetOriginalHealth, target->GetMaxHealth()); + + newHeal = CalculateHealScaling(heal, targetOriginalHealth, target->GetMaxHealth()); + } else { + newHeal = heal; + } + MpLogger::debug("No creature data found for owner {}, using current level for scaling", ownerCreature->GetGUID().ToString()); + } + } + } + else { + MpLogger::error("Invalid creature data ScaleHealSpell()"); + return heal * totalModifier; + } + } else { + // Scale heal based on target's health, not caster's health + if (target) { + // Get target's original instance health for scaling comparison + MpCreatureData* targetCreatureData = sMpDataStore->GetCreatureData(target->GetGUID()); + uint32 targetOriginalHealth = targetCreatureData && targetCreatureData->originalInstanceHealth > 0 ? + targetCreatureData->originalInstanceHealth : target->GetMaxHealth(); + MpLogger::debug("HEALING: >>> Scaling heal to target: {} Original Instance Health: {} New Health: {}", target->GetName(), targetOriginalHealth, target->GetMaxHealth()); + newHeal = CalculateHealScaling(heal, targetOriginalHealth, target->GetMaxHealth()); + } else { + // No target available, use original heal + newHeal = heal; + } } - int32 originalLevel = creatureData->originalLevel; + // Calculate the additional heal from scaling (scaled heal - original heal) + int32 additionalHeal = newHeal - heal; - float levelDifference = creature->GetLevel() - originalLevel; - float spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), creature->GetInstanceId()); + // Apply the scaling modifier to the additional heal only + int32 scaledAdditionalHeal = additionalHeal * totalModifier; - // Reduced scaling for heals to prevent them from being too powerful - float scalingFactor = CalculateScaling(levelDifference, spellBonus * 0.4f, 1.8f); // Reduced scaling factor and constant + // Use the diminishing return values from the configuration (same as damage) + uint32 threshold = sMythicPlus->diminishingThresholds[instanceData->difficulty]; + float diminishingExponent = sMythicPlus->diminishingExponent; - MpLogger::debug(" >>> Spell {} healed scaled from for spell New Heal: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], heal, scalingFactor, healMultiplier); - return int32(heal * scalingFactor * healMultiplier); + // Apply diminishing returns only to the additional scaled heal if it exceeds threshold * 2 since enemies have much more health. + if (scaledAdditionalHeal > threshold * 2.0f) { + // Calculate the diminished excess additional heal + float excess = scaledAdditionalHeal - threshold; + float diminishedExcess = pow(excess, diminishingExponent * 0.95f); // slightly reduce the diminishing returns for heals + scaledAdditionalHeal = threshold + diminishedExcess; + + MpLogger::debug("HEALING: >>> Above Diminishing Threshold for Heal Spell {} - Original: {}, Additional: {}, Diminished Additional: {}, Final: {}", + spellInfo->SpellName[0], heal, additionalHeal * totalModifier, scaledAdditionalHeal, heal + scaledAdditionalHeal); + } else { + MpLogger::debug("HEALING: >>> Below Diminishing Threshold for Heal Spell {} - Original: {}, Additional: {}, Final: {}", + spellInfo->SpellName[0], heal, scaledAdditionalHeal, heal + scaledAdditionalHeal); + } + + // If this is a heroic instance the additional heal be only slightly increased + InstanceMap* instanceMap = creature->GetMap()->ToInstanceMap(); + if (instanceMap && (instanceMap->IsHeroic() || instanceMap->Is25ManRaid())) { + scaledAdditionalHeal = scaledAdditionalHeal * 1.15f; + } + + // Return original heal + scaled additional heal (with potential diminishing returns) + return heal + scaledAdditionalHeal; } void MythicPlus::GroupReset(Group* /*group*/, Map* /* map */) { @@ -640,14 +827,14 @@ uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uin float healthVariation; // This is the fine grained hpScaleFactor set for the instance (and/or) creature overrides in the database. - float hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty); + int32 hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty); // Add some variance to the healthpool so enemies are not all the same if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->isElite() || cInfo->rank == CREATURE_ELITE_RARE) { healthVariation = frand(1.0f, 1.15f); } else { // This addresses Normals and other trash from getting to big a HP bonus healthVariation = frand(1.0f, 1.05f); - hpScaleFactor *= 0.40; + hpScaleFactor *= 0.50; } // Add in special overrides here as necessary: @@ -656,7 +843,20 @@ uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uin } float unitTypeMod = GetTypeHealthModifier(rank); - uint32 basehp = uint32(std::ceil(origHealth * healthVariation)); + uint32 basehp; + + // Only apply unitTypeMod for non-normal enemies + if (rank != CREATURE_ELITE_NORMAL) { + basehp = uint32(std::ceil(origHealth * healthVariation * unitTypeMod)); + } else { + basehp = uint32(std::ceil(origHealth * healthVariation)); + } + + // if it is a heroic instance give the enemy an additional 20% boost + InstanceMap* instanceMap = creature->GetMap()->ToInstanceMap(); + if (instanceMap && (instanceMap->IsHeroic() || instanceMap->Is25ManRaid())) { + basehp *= 1.25f; + } /** * @brief Calculating the final creature health encompasses all the potential modifiers @@ -677,7 +877,7 @@ uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uin } // Calculates a balanced growth curve that provides good scaling across all level ranges -float CalculateScaling(int levelDifference, float scaleFactor, float constant, float growthFactor) { +float CalculateScaling(int levelDifference, float scaleFactor, float constant, float /*growthFactor*/) { float levelMultiplier; if (levelDifference <= 0) { diff --git a/src/MythicPlus.h b/src/MythicPlus.h index 74b17c1..fbf3853 100644 --- a/src/MythicPlus.h +++ b/src/MythicPlus.h @@ -69,10 +69,14 @@ public: uint32 legendaryItemOffset; uint32 ascendantItemOffset; - // Scaling modifiers + // Scaling modifiers (Deprecated) uint32 meleeAttackPowerDampener; uint32 meleeAttackPowerStart; + // Spell Damage Diminishing Returns + float diminishingExponent; + std::unordered_map diminishingThresholds; + enum MP_UNIT_EVENT_TYPE { UNIT_EVENT_MELEE, @@ -135,6 +139,9 @@ public: // Calculate spell damage based on player health pools int32 CalculateSpellDamage(uint32 baseDamage, int originalLevel, int targetLevel); + // Calculate heal scaling based on creature health percentages + int32 CalculateHealScaling(uint32 baseHeal, uint32 originalHealth, uint32 currentMaxHealth); + static bool IsFinalBoss(Creature* creature); static void GroupReset(Group* group, Map* map); diff --git a/src/Scripts/UnitScript.cpp b/src/Scripts/UnitScript.cpp index 3e669ed..7a80144 100644 --- a/src/Scripts/UnitScript.cpp +++ b/src/Scripts/UnitScript.cpp @@ -2,12 +2,200 @@ #include "Player.h" #include "MythicPlus.h" #include "ScriptMgr.h" +#include "SpellAuraEffects.h" class MythicPlus_UnitScript : public UnitScript { public: MythicPlus_UnitScript() : UnitScript("MythicPlus_UnitScript", true) { } +private: + // Helper function to determine if a spell scales with Attack Power + bool IsAttackPowerScalingSpell(SpellInfo const* spellInfo) { + if (!spellInfo || spellInfo->Effects.empty()) { + return false; + } + + auto mainEffect = spellInfo->Effects[0]; + + // Check 1: Direct weapon damage effects + bool isWeaponEffect = (mainEffect.Effect == SPELL_EFFECT_WEAPON_DAMAGE || + mainEffect.Effect == SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL || + mainEffect.Effect == SPELL_EFFECT_NORMALIZED_WEAPON_DMG || + mainEffect.Effect == SPELL_EFFECT_WEAPON_PERCENT_DAMAGE); + + // Check 2: Damage class indicates melee/ranged (scales with AP) + bool isMeleeOrRanged = (spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || + spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED); + + // Check 3: Requires weapon equipment + bool requiresWeapon = (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON); + + // Check 4: Specific spell families known to scale with AP + bool isKnownAPSpell = false; + if (spellInfo->SpellFamilyName == SPELLFAMILY_ROGUE) { + // Rogue poisons and weapon-based abilities + isKnownAPSpell = (spellInfo->SpellFamilyFlags[0] & 0x10000) || // Deadly Poison flag + (spellInfo->SpellFamilyFlags[1] & 0x80000); // Other poison flags + } + + // Return true if any indicator suggests AP scaling + return (isWeaponEffect || isMeleeOrRanged || requiresWeapon || isKnownAPSpell); + } + + /** + * @brief This functions processes spell damage for DOTs and Direct Damage Spells it + * handles special cases for Melee scaling spells and AP scaling spells also so they + * are not scaled up twice and murder all my friends + * + * @tparam DamageType + * @param target + * @param attacker + * @param damage + * @param spellInfo + * @param eventType + * @param logPrefix + */ + + template + void ProcessSpellDamage(Unit* target, Unit* attacker, DamageType& damage, SpellInfo const* spellInfo, MythicPlus::MP_UNIT_EVENT_TYPE eventType, const std::string& logPrefix) { + if(damage == 0) { + return; + } + + // Check if attacker is a GameObject (traps, totems, environmental hazards) + if (!attacker || !attacker->ToCreature()) { + MpLogger::debug("SPELL SCALING: Attacker is not a creature (likely GameObject or null), skipping CalcValue reversal"); + // Apply Mythic+ scaling to original damage without CalcValue reversal + damage = static_cast(modifyIncomingDmgHeal(eventType, target, attacker, static_cast(damage), spellInfo)); + return; + } + + // Debug: Log spell effects to understand what we're dealing with + // if (spellInfo->Effects.size() > 0) { + // auto mainEffect = spellInfo->Effects[0]; + + // MpLogger::debug("{}: Incoming Damage: {} Spell {} (ID: {}) Family: {} has RealPointsPerLevel: {} and BasePoints: {} and DieSides: {} and School: {}", logPrefix, + // damage, + // spellInfo->SpellName[0], + // spellInfo->Id, + + // mainEffect.RealPointsPerLevel, + // mainEffect.BasePoints, + // mainEffect.DieSides, + // spellInfo->SchoolMask); + // } else { + // MpLogger::debug("{}: Incoming Damage: {} Spell {} (ID: {}) has no effects and School: {}", logPrefix, + // damage, + // spellInfo->SpellName[0], + // spellInfo->Id, + // spellInfo->SchoolMask); + + // return; + // } + + Creature* creatureCaster = attacker->ToCreature(); + MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creatureCaster->GetGUID()); + + if (!creatureCaster) { + MpLogger::error("Creature caster is null in map {}", attacker ? attacker->GetMap()->GetId() : 0); + return; + } + if (!creatureData) { + MpLogger::error("Failed to get creature data for {} in map {}", creatureCaster->GetName(), attacker ? attacker->GetMap()->GetId() : 0); + return; + } + + // Check if this spell scales with Attack Power + if (IsAttackPowerScalingSpell(spellInfo)) { + + // need another special case here to determine if a spell was not scaled up by AP meaning the incoming damage is close to the + // original effect of the spell and therefore should instead use spell effect scaling it should be no more than 15% of the original effect + bool notScaledByAP = false; + if (spellInfo && !spellInfo->Effects.empty()) { + int32 baseEffect = spellInfo->Effects[0].CalcValue(attacker, nullptr, nullptr); + if (damage <= (baseEffect * 1.15f)) { + MpLogger::debug(">>>> MELEE SPELL SCALING: Spell {} (ID: {}) is not scaled by AP damage: {} vs originalEffect: {}", + spellInfo->SpellName[0], spellInfo->Id, damage, baseEffect); + notScaledByAP = true; + } + } else { + // If we can't determine the base effect, default to treating it as not AP-scaled + notScaledByAP = true; + MpLogger::debug(">>>> MELEE SPELL SCALING: Could not determine base effect for spell, defaulting to spell scaling"); + } + + // if the effect type of the spell is not physical (aka not mitigated by armor/defense) then it needs to instead have the typical + // spell damage multiplier applied instead of melee damage scaling + if (! notScaledByAP && (spellInfo->SchoolMask == SPELL_SCHOOL_NORMAL || spellInfo->SchoolMask == SPELL_SCHOOL_MASK_NORMAL)) { + uint32 meleeDamage = static_cast(std::max(0, static_cast(damage))); + + damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_MELEE, target, attacker, meleeDamage); + + MpLogger::debug(">>MELEE SPELL SCALING: {} hits with spell: {} ID: {} meleeDamage: {} damage: {}", attacker->GetName(), spellInfo->SpellName[0], spellInfo->Id, meleeDamage, damage); + } else { + + // get the creatures original attack power + SpellEffectInfo const& effect = spellInfo->Effects[0]; + uint32 spellDmg = static_cast(effect.CalcValue(attacker, nullptr, nullptr) * effect.CalcDamageMultiplier(attacker, nullptr)); + + // now take the original attack power * 0.08 and add it to the spell damage + uint32 apDmg = static_cast(creatureData->originalStats->AttackPower * 0.10f); + uint32 finalDmg = spellDmg + apDmg; + + MpLogger::debug(">> AP BASED DAMAGE Scaledown: origDamage: {} | spellDmg: {} | apDmg: {} | finalDmg: {}", static_cast(damage), spellDmg, apDmg, finalDmg); + + damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_SPELL, target, attacker, finalDmg, spellInfo); + + // need to take into consideration if this is a stacking spell and multiply the final damage by the number of stacks + if(spellInfo->AttributesEx3 & SPELL_ATTR3_DOT_STACKING_RULE) { + + Aura* aura = target->GetAura(spellInfo->Id, attacker->GetGUID()); + uint32 stacks = aura ? aura->GetStackAmount() : 1; + if(aura) { + damage *= stacks; + } + } + + } + return; + } + + // Reverse the CalcValue scaling to get original base damage + int32 originalDamage = static_cast(damage); + + if (creatureCaster && creatureData && creatureData->originalLevel < 70) { + CreatureTemplate const* cInfo = creatureCaster->GetCreatureTemplate(); + + // Get the scaling factors used in CalcValue + CreatureBaseStats const* pCBS = sObjectMgr->GetCreatureBaseStats(creatureCaster->GetLevel(), creatureCaster->getClass()); + float CBSPowerCreature = pCBS->BaseDamage[cInfo->expansion]; + + uint32 tempLevel = spellInfo->SpellLevel; + if(tempLevel == 0) { + tempLevel = creatureData->originalLevel; + } + + CreatureBaseStats const* spellCBS = sObjectMgr->GetCreatureBaseStats(tempLevel, creatureCaster->getClass()); + float CBSPowerSpell = spellCBS->BaseDamage[cInfo->expansion]; + + // MpLogger::debug("SPELL SCALING: Creature Lvl {} -> {} | Spell Lvl {} | tempLevel: {} | CBSPowerCreature: {} CBSPowerSpell: {}", + // creatureData->originalLevel, creatureCaster->GetLevel(), tempLevel, CBSPowerCreature, CBSPowerSpell); + + // Reverse the CalcValue scaling: originalDamage = scaledDamage / (CBSPowerCreature / CBSPowerSpell) + if (CBSPowerCreature > 0.0f) { + originalDamage = static_cast(static_cast(damage) * (CBSPowerSpell / CBSPowerCreature)); + // MpLogger::debug("SPELL SCALING: Reversed CalcValue scaling - Scaled: {} -> Original: {} (Factor: {:.2f})", + // damage, originalDamage, CBSPowerSpell / CBSPowerCreature); + } + } + + // Apply Mythic+ scaling to the original base damage + damage = static_cast(modifyIncomingDmgHeal(eventType, target, attacker, static_cast(originalDamage), spellInfo)); + } + +public: + void ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage, SpellInfo const* spellInfo) override { if (!target && !attacker) { return; @@ -32,16 +220,20 @@ public: } } - if(isHot) { damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_HOT, target, attacker, damage, spellInfo); } else { - damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_DOT, target, attacker, damage, spellInfo); + ProcessSpellDamage(target, attacker, damage, spellInfo, MythicPlus::UNIT_EVENT_DOT, "DOT DAMAGE"); } } void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage, SpellInfo const* spellInfo) override { if (!target && !attacker) { + + if(spellInfo) { + MpLogger::info("ModifySpellDamageTaken: Target and attacker are null for spell: {} ID: {}", spellInfo->SpellName[0], spellInfo->Id); + } + return; } @@ -50,11 +242,17 @@ public: return; } - if(sMythicPlus->EligibleDamageTarget(target)) { - MpLogger::debug("ModifySpellDamageTaken: {} hits {} with spell: {}", attacker->GetName(), target->GetName(), spellInfo->SpellName[0]); + if(!sMythicPlus->EligibleDamageTarget(target)) { + if(spellInfo) { + MpLogger::info("ModifySpellDamageTaken: Target is not eligible for spell: {} ID: {}", spellInfo->SpellName[0], spellInfo->Id); + } + return; } - damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_SPELL, target, attacker, damage, spellInfo); + MpLogger::debug("ModifySpellDamageTaken: {} hits {} with spell: {} ID: {}", attacker ? attacker->GetName() : "[null]", target ? target->GetName() : "[null]", spellInfo ? spellInfo->SpellName[0] : "[no spell]", spellInfo ? spellInfo->Id : 0); + + // Use the generic ProcessSpellDamage function + ProcessSpellDamage(target, attacker, damage, spellInfo, MythicPlus::UNIT_EVENT_SPELL, "SPELL DAMAGE"); } /** @@ -88,8 +286,93 @@ public: healing = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_HEAL, target, healer, healing, spellInfo); } + void OnAuraApply(Unit* unit, Aura* aura) override { + if (!unit || !aura) { + return; + } + + // Only scale auras for players + if (!unit->IsPlayer()) { + return; + } + + #if defined(MOD_PRESENT_NPCBOTS) + if (unit->IsNPCBotOrPet()) { + return; + } + #endif + + Map* map = unit->GetMap(); + if (!sMythicPlus->IsMapEligible(map)) { + return; + } + + // Get instance data for scaling factors + MpInstanceData* instanceData = sMpDataStore->GetInstanceData(map->GetId(), map->GetInstanceId()); + if (!instanceData) { + return; + } + + SpellInfo const* spellInfo = aura->GetSpellInfo(); + if (!spellInfo) { + return; + } + + Creature* creatureCaster = aura->GetCaster()->ToCreature(); + if (!creatureCaster) { + return; + } + + // MpLogger::debug("Aura Apply: {} applied to {} by {} Id: {}", spellInfo->SpellName[0], unit->GetName(), aura->GetCaster()->GetName(), aura->GetId()); + + // Scale aura effects based on creature type and instance difficulty + float scaleFactor = 1.0f; + + if (creatureCaster->IsDungeonBoss() || creatureCaster->isWorldBoss()) { + scaleFactor = instanceData->boss.spell * 0.8f; // Reduce boss aura scaling slightly + } else { + scaleFactor = instanceData->creature.spell * 0.7f; // Reduce normal creature aura scaling + } + + // Apply scaling to stat modifier effects only (damage auras handled elsewhere) + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { + AuraEffect* effect = aura->GetEffect(i); + if (!effect) { + continue; + } + + auto amount = effect->GetAmount(); + // MpLogger::debug("Aura Effect type: {} amount: {}", effect->GetAuraType(), amount); + + // // Only scale stat modifiers and resistances (not damage/healing effects) + // if (effect->GetAuraType() == SPELL_AURA_MOD_STAT || + // effect->GetAuraType() == SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE || + // effect->GetAuraType() == SPELL_AURA_MOD_RESISTANCE || + // effect->GetAuraType() == SPELL_AURA_MOD_RESISTANCE_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_BASE_RESISTANCE_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_DAMAGE_DONE || + // effect->GetAuraType() == SPELL_AURA_MOD_DAMAGE_TAKEN || + // effect->GetAuraType() == SPELL_AURA_MOD_HEALING_DONE || + // effect->GetAuraType() == SPELL_AURA_MOD_HEALING_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_HEALING_TAKEN_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_ARMOR_PENETRATION_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_ATTACK_POWER || + // effect->GetAuraType() == SPELL_AURA_MOD_ATTACK_POWER_PCT || + // effect->GetAuraType() == SPELL_AURA_MOD_SPELL_POWER_PCT) { + + // int32 originalAmount = effect->GetAmount(); + // int32 newAmount = int32(originalAmount * scaleFactor); + // effect->ChangeAmount(newAmount); + + // MpLogger::debug("AURA SCALING: Scaled {} stat modifier effect {} from {} to {} (factor: {:.2f})", + // spellInfo->SpellName[0], i, originalAmount, newAmount, scaleFactor); + // } + } + } + uint32 modifyIncomingDmgHeal(MythicPlus::MP_UNIT_EVENT_TYPE eventType,Unit* target, Unit* attacker, uint32 damageOrHeal, SpellInfo const* spellInfo = nullptr) { if (!target && !attacker) { + MpLogger::info("modifyIncomingDmgHeal: Target and attacker are null for event {}", eventType); return damageOrHeal; } @@ -105,7 +388,7 @@ public: } #if defined(MOD_PRESENT_NPCBOTS) - if (attacker && attacker->IsNPCBot()) { + if (attacker && attacker->IsNPCBotOrPet()) { return damageOrHeal; } #endif @@ -153,24 +436,25 @@ public: } else { alteredDmgHeal = damageOrHeal * instanceData->creature.melee; } - MpLogger::debug("Incoming Melee New Damage: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName()); + // MpLogger::debug("Incoming Melee New Damage: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName()); + break; case MythicPlus::UNIT_EVENT_DOT: case MythicPlus::UNIT_EVENT_SPELL: if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->GetEntry() == 23682) { if(spellInfo) { - MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); + // MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->boss.spell); } else { alteredDmgHeal = damageOrHeal * instanceData->boss.spell; - MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); + // MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); } } else { if(spellInfo) { - MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); + // MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->creature.spell); } else { - MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); + // MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal); alteredDmgHeal = damageOrHeal * instanceData->creature.spell; } } @@ -188,18 +472,22 @@ public: if(sMythicPlus->EligibleHealTarget(target) && (eventType == MythicPlus::UNIT_EVENT_HEAL || eventType == MythicPlus::UNIT_EVENT_HOT)) { if(creature->IsDungeonBoss()) { if(spellInfo) { - alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->boss.spell); + alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->boss.spell * 0.7f); } else { - alteredDmgHeal = damageOrHeal * instanceData->boss.spell; + alteredDmgHeal = damageOrHeal * instanceData->boss.spell * 0.7f; } } else { if(spellInfo) { - alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->creature.spell); + alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->creature.spell * 0.7f); } else { - alteredDmgHeal = damageOrHeal * instanceData->creature.spell; + alteredDmgHeal = damageOrHeal * instanceData->creature.spell * 0.70f; } } - MpLogger::debug("Incoming heal: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName()); + MpLogger::debug("Incoming heal: {}({}) {} hits {}", + alteredDmgHeal, + damageOrHeal, + attacker ? attacker->GetName() : "[null]", + target ? target->GetName() : "[null]"); } return alteredDmgHeal > 0 ? alteredDmgHeal : damageOrHeal; diff --git a/src/Scripts/WorldScript.cpp b/src/Scripts/WorldScript.cpp index 98b9d2f..0859431 100644 --- a/src/Scripts/WorldScript.cpp +++ b/src/Scripts/WorldScript.cpp @@ -89,8 +89,17 @@ public: sMythicPlus->legendaryItemOffset = sConfigMgr->GetOption("MythicPlus.Legendary.ItemOffset", 21000000); sMythicPlus->ascendantItemOffset = sConfigMgr->GetOption("MythicPlus.Ascendant.ItemOffset", 22000000); + // Deprecated part of old attack power scaling sMythicPlus->meleeAttackPowerDampener = sConfigMgr->GetOption("MythicPlus.MeleeAttackPowerDampener", 2000); sMythicPlus->meleeAttackPowerStart = sConfigMgr->GetOption("MythicPlus.MeleeAttackPowerStart", 10000); + + // Get diminishing returns from configuration + sMythicPlus->diminishingExponent = sConfigMgr->GetOption("MythicPlus.DiminishingExponent", 0.975f); + sMythicPlus->diminishingThresholds = { + {MpDifficulty::MP_DIFFICULTY_MYTHIC, sConfigMgr->GetOption("MythicPlus.DiminishingThreshold.Mythic", 10000)}, + {MpDifficulty::MP_DIFFICULTY_LEGENDARY, sConfigMgr->GetOption("MythicPlus.DiminishingThreshold.Legendary", 20000)}, + {MpDifficulty::MP_DIFFICULTY_ASCENDANT, sConfigMgr->GetOption("MythicPlus.DiminishingThreshold.Ascendant", 40000)} + }; } void OnStartup() override