Updates to enemy scaling mechanics for heroic, raids

This commit is contained in:
2025-08-01 10:06:55 -04:00
parent 64dd608dc4
commit 3f3f63f90e
7 changed files with 587 additions and 65 deletions

View File

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

View File

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

View File

@@ -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<std::string> 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;

View File

@@ -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<int32>(std::ceil(percentDamage * targetHpPool)));
int32 scaledDamage = static_cast<int32>(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<float>(baseHeal) / static_cast<float>(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<int32>(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<uint32>(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) {

View File

@@ -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<MpDifficulty, uint32> 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);

View File

@@ -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<typename DamageType>
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<DamageType>(modifyIncomingDmgHeal(eventType, target, attacker, static_cast<uint32>(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<uint32>(std::max(0, static_cast<int32>(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<uint32>(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<uint32>(creatureData->originalStats->AttackPower * 0.10f);
uint32 finalDmg = spellDmg + apDmg;
MpLogger::debug(">> AP BASED DAMAGE Scaledown: origDamage: {} | spellDmg: {} | apDmg: {} | finalDmg: {}", static_cast<int32>(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<int32>(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<int32>(static_cast<int32>(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<DamageType>(modifyIncomingDmgHeal(eventType, target, attacker, static_cast<uint32>(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;

View File

@@ -89,8 +89,17 @@ public:
sMythicPlus->legendaryItemOffset = sConfigMgr->GetOption<uint32>("MythicPlus.Legendary.ItemOffset", 21000000);
sMythicPlus->ascendantItemOffset = sConfigMgr->GetOption<uint32>("MythicPlus.Ascendant.ItemOffset", 22000000);
// Deprecated part of old attack power scaling
sMythicPlus->meleeAttackPowerDampener = sConfigMgr->GetOption<uint32>("MythicPlus.MeleeAttackPowerDampener", 2000);
sMythicPlus->meleeAttackPowerStart = sConfigMgr->GetOption<uint32>("MythicPlus.MeleeAttackPowerStart", 10000);
// Get diminishing returns from configuration
sMythicPlus->diminishingExponent = sConfigMgr->GetOption<float>("MythicPlus.DiminishingExponent", 0.975f);
sMythicPlus->diminishingThresholds = {
{MpDifficulty::MP_DIFFICULTY_MYTHIC, sConfigMgr->GetOption<uint32>("MythicPlus.DiminishingThreshold.Mythic", 10000)},
{MpDifficulty::MP_DIFFICULTY_LEGENDARY, sConfigMgr->GetOption<uint32>("MythicPlus.DiminishingThreshold.Legendary", 20000)},
{MpDifficulty::MP_DIFFICULTY_ASCENDANT, sConfigMgr->GetOption<uint32>("MythicPlus.DiminishingThreshold.Ascendant", 40000)}
};
}
void OnStartup() override