#include "MythicPlus.h" #include "MpLogger.h" #include "ObjectMgr.h" #include "MapMgr.h" #include "ScriptMgr.h" #include "Group.h" #include "Unit.h" #include "WorldPacket.h" #include "UpdateMask.h" #include "MpScriptAI.h" #include #include // Special case for Headless Horseman Event const uint32 HEADLESS_HORSEMAN = 23682; bool MythicPlus::IsMapEligible(Map* map) { if (!Enabled) { return false; } if (map->IsDungeon()) { return true; } return false; } bool MythicPlus::IsDifficultySet(Player const* player) { Group const* group = player->GetGroup(); if (!group) { return false; } MpGroupData const* groupData = sMpDataStore->GetGroupData(group->GetGUID()); if (!groupData) { return false; } return true; } bool MythicPlus::IsDifficultyEnabled(std::string difficulty) { return std::find(enabledDifficulties.begin(), enabledDifficulties.end(), difficulty) != enabledDifficulties.end(); } bool MythicPlus::IsDungeonDisabled(uint32 dungeon) { return std::find(disabledDungeons.begin(), disabledDungeons.end(), dungeon) != disabledDungeons.end(); } bool MythicPlus::EligibleHealTarget(Unit* target) { if (!target) { return false; } if (target->GetTypeId() == TYPEID_CORPSE || target->GetTypeId() == TYPEID_GAMEOBJECT) { return false; } #if defined(MOD_PRESENT_NPCBOTS) if (target->IsNPCBot()) { return false; } // Null check for GetOwner to avoid dereferencing a null pointer if ((target->IsPet() || target->IsSummon() || target->IsHunterPet()) && target->GetOwner() && target->GetOwner()->IsNPCBot()) { return false; } #endif // Ensure target is a valid creature before checking eligibility Creature* creatureTarget = target->ToCreature(); if (creatureTarget && sMythicPlus->IsCreatureEligible(creatureTarget)) { return true; } return false; } bool MythicPlus::EligibleDamageTarget(Unit* target) { if (!target) { return false; } if (target->GetTypeId() == TYPEID_PLAYER) { return true; } #if defined(MOD_PRESENT_NPCBOTS) if (target->IsNPCBot()) { return true; } if ((target->IsPet() || target->IsSummon() || target->IsHunterPet()) && target->GetOwner() && target->GetOwner()->IsNPCBot()) { return true; } #endif Creature* creature = target->ToCreature(); if (creature && (creature->IsPet() || creature->IsSummon() || creature->IsHunterPet()) && creature->GetOwner() && creature->IsControlledByPlayer()) { return true; } return false; } bool MythicPlus::IsCreatureEligible(Creature* creature) { if (!creature) { return false; } std::string scriptName = creature->GetScriptName(); if(scriptName.starts_with("boss_")) { return true; } if (creature->IsDungeonBoss()) { return true; } if (creature->GetEntry() == HEADLESS_HORSEMAN) { return true; } // Check if the creature is a pet or summon controlled by a player if ((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) { return false; } // Skip critters, totems, and triggers if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger()) { return false; } #if defined(MOD_PRESENT_NPCBOTS) // Safely check if the creature is an NPC Bot if (creature->IsNPCBot()) { return false; } if(creature->GetBotOwner()) { return false; } #endif // Check for NPC-related flags (vendor, gossip, quest giver, trainer, etc.) if ((creature->IsVendor() || creature->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP) || creature->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER) || creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER) || creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER_PROFESSION) || creature->HasNpcFlag(UNIT_NPC_FLAG_REPAIR) || creature->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || creature->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) && (!creature->IsDungeonBoss())) { return false; } return true; } void MythicPlus::AddCreatureForScaling(Creature* creature) { if (!IsCreatureEligible(creature)) { return; } sMpDataStore->AddCreatureData(creature->GetGUID(), MpCreatureData(creature)); // MpLogger::debug("Added creature {} to instance data for instance {}", // creature->GetName(), // creature->GetMap()->GetMapName() // ); } void MythicPlus::AddScaledCreature(Creature* creature, MpInstanceData* instanceData) { MpCreatureData creatureData = MpCreatureData(creature); creatureData.SetScaled(true); creatureData.SetDifficulty(instanceData->difficulty); creatureData.lastDeathState = creature->getDeathState(); sMpDataStore->AddCreatureData(creature->GetGUID(), creatureData); // allow small variance in level for non-boss creatures uint8 level = uint8(urand(instanceData->creature.avgLevel - 1, instanceData->creature.avgLevel + 1)); if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) { ScaleCreature(instanceData->boss.avgLevel, creature, &instanceData->boss, instanceData->difficulty); } else { ScaleCreature(level, creature, &instanceData->creature, instanceData->difficulty); } // Update AI now the creature has been scaled. // auto ai = new MpScriptAI(creature, instanceData->difficulty); // creature->SetAI(ai); // We know the creature is scaled and in the instance to fire the event. // sCreatureHooks->AddToInstance(creature); // MpLogger::debug("Scaled Creature {} Entry {} Id {} level from {} to {}", // creature->GetName(), // creature->GetEntry(), // creature->GetGUID().GetCounter(), // creature->GetLevel(), // level // ); } void MythicPlus::ScaleRemaining(Player* player, MpInstanceData* instanceData) { std::vector creatures = sMpDataStore->GetUnscaledCreatures(player->GetMapId(), player->GetInstanceId()); for (MpCreatureData* creatureData : creatures) { AddScaledCreature(creatureData->creature, instanceData); } } void MythicPlus::ScaleAll(Player* player, MpInstanceData* instanceData) { std::vector creatures = sMpDataStore->GetInstanceCreatures(player->GetMapId(), player->GetInstanceId()); for (MpCreatureData* creatureData : creatures) { // Only scale living creatures if (creatureData->creature && creatureData->creature->IsAlive()) { ScaleCreature(creatureData->creature->GetLevel(), creatureData->creature, &instanceData->creature, instanceData->difficulty); } } } // Perform any memory cleanup when the creature is removed from the world and no longer needed. void MythicPlus::RemoveCreature(Creature* creature) { MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID()); if (!creatureData) { return; } sMpDataStore->RemoveCreatureData(creature->GetGUID()); } void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* multipliers, MpDifficulty difficulty) { 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, cInfo->unit_class ); // Scale the creatures base health uint32 basehp = stats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING]; uint32 health = CalculateNewHealth(creature, cInfo, mapId, difficulty, basehp, multipliers->health); creature->SetCreateHealth(health); creature->SetMaxHealth(health); creature->SetHealth(health); creature->ResetPlayerDamageReq(); creature->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)health); /** * @TODO: Figure out mana later for unit_types 2 and 8 */ // Scale the creatures mana pool uint32 mana = uint32(std::ceil(stats->BaseMana * cInfo->ModMana)); creature->SetCreateMana(mana); creature->SetMaxPower(POWER_MANA, mana); creature->SetPower(POWER_MANA, mana); if(cInfo->unit_class == UNIT_CLASS_MAGE) { creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 10.0f); } if(cInfo->unit_class == UNIT_CLASS_PALADIN) { creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 3.0f); } MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId()); // 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.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 *= normalEnemyReducer; rangeAp *= normalEnemyReducer; } MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID()); if(creatureData) { creatureData->NewAttackPower = ap; creatureData->AttackPowerScaleMultiplier = meleeMultiplier; } // Set scaled attack power creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, ap); creature->SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, rangeAp); // set the base weapon damage 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.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(); // Scale up the armor with some variance also to make some tougher enemies in the mix 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) { float origHpPool = sMpDataStore->GetPlayerHealthAvg(originalLevel); float targetHpPool = sMpDataStore->GetPlayerHealthAvg(targetLevel); // 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) { MpLogger::error("Invalid spell info ScaleDamageSpell()"); return damage; } MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId()); if (!instanceData) { MpLogger::debug("No instance data found for spell scaling, using original damage"); return damage; } 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; // If for some reason there is not a creature, just use the global modifier x instance modifier if(!creature) { MpLogger::error("Invalid creature ScaleDamageSpell()"); return damage * totalModifier; } // Use the already calculated damage as the base for scaling int32 newDamage = damage; // Handle Summoned unit modifiers as if(!creatureData) { // handle if bot pets if NPCBot is installed. #ifdef NPCBOT if(creature->IsNPCBotOrPet()) { return damage; } #endif // 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) { 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 * normalEnemyReducer; } 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 * normalEnemyReducer; } newDamage = CalculateSpellDamage(damage, ownerCreature->GetLevel(), ownerCreature->GetLevel()); MpLogger::debug("No creature data found for owner {}, using current level for scaling", ownerCreature->GetGUID().ToString()); } } } else { MpLogger::error("Invalid creature data ScaleDamageSpell()"); return damage * totalModifier; } } else { 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.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; // Use the diminishing return values from the configuration uint32 threshold = sMythicPlus->diminishingThresholds[instanceData->difficulty]; float diminishingExponent = sMythicPlus->diminishingExponent; // 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("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) { if (!spellInfo) { MpLogger::error("Invalid spell info ScaleHealSpell()"); return heal; } 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 heal * totalModifier; } // 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 * normalEnemyReducer; // 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 * normalEnemyReducer; // 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; } } // Calculate the additional heal from scaling (scaled heal - original heal) int32 additionalHeal = newHeal - heal; // Apply the scaling modifier to the additional heal only int32 scaledAdditionalHeal = additionalHeal * totalModifier; // Use the diminishing return values from the configuration (same as damage) uint32 threshold = sMythicPlus->diminishingThresholds[instanceData->difficulty]; float diminishingExponent = sMythicPlus->diminishingExponent; // 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 */) { // Stubbed out for later implementation } bool MythicPlus::IsFinalBoss(Creature* creature) { std::array finalBosses = { // --- WoW Classic Dungeons --- 11519, /* Bazzalan Ragefire */ 639, /* Edwin VanCleef Deadmines */ 3654, /* Mutanus the Devourer Wailing Caverns */ 4275, /* Archmage Arugal Shadowfang Keep */ 1716, /* Bazil Thredd Stockades */ 4829, /* Aku'mai blackfathom Deeps */ 7800, /* Mekgineer Thermaplugg Gnomeregan */ 4421, /* Charlga Razorflank Razorfen Kraul */ 4543, /* Bloodmage Thalnos Scarlet Monastery */ 3975, /* Herod Scarlet Monastery */ 3977, /* High Inquisitor Whitemane Scarlet Monastery */ 7350, /* Amnennar the Coldbringer Razorfen Downs */ 2748, /* Archaedas Uldaman */ 7267, /* Chief Ukorz Sandscalp Zul'Farrak */ 12201, /* Princess Theradras Maraudon */ 5709, /* Shade of Eranikus Sunken Temple */ 9019, /* Emperor Dagran Thaurissan Blackrock Depths */ 9568, /* Overlord Wyrmthalak Lower Blackrock Spire */ 10363, /* General Drakkisath Upper Blackrock Spire */ 11492, /* alzzin the Wildshaper Dire Maul */ 11496, /* Immol'thar Dire Maul */ 11501, /* King Gordok Dire Maul */ 1853, /* Darkmaster Gandling Scholomance */ 10812, /* Grand Crusader Dathrohan Stratholme */ 10440, /* Baron Rivendare Stratholme */ // --- WoW Classic Raids --- 11583, /* Nefarian Blackwing Lair */ 11502, /* Ragnaros Molten Core */ 14834, /* Hakkar Zul'Gurub */ 15727, /* C'Thun Temple of Ahn'Qiraj */ 15339, /* Ossirian the Unscarred Ruins of Ahn'Qiraj */ // --- The Burning Crusade --- 17536, /* Nazan - Hellfire Ramparts Normal */ 17536, /* Nazan - Hellfire Ramparts Normal */ 17377, /* Kelidan the Breaker - Blood Furnace Normal */ 18607, /* Kelidan the Breaker - Blood Furnace Heroic */ 17942, /* Quagmirran - The Slave Pens Normal */ 19894, /* Quagmirran - The Slave Pens Heoric */ 17882, /* The Black Stalker - The Underbog Normal */ 20184, /* The Black Stalker - The Underbog Heroic */ 24420, /* The Black Stalker - The Underbog Heroic(2) */ 18344, /* Nexus-Prince Shaffar - Mana-Tombs Normal */ 20256, /* Nexus-Prince Shaffar - Mana-Tombs Heroic */ 18373, /* Exarch Maladaar - Auchenai Crypts Normal */ 20306, /* Exarch Maladaar - Auchenai Crypts Heoric */ 18096, /* Epoch Hunter - Old Hillsbrad Foothills Normal */ 20531, /* Epoch Hunter - Old Hillsbrad Foothills Heroic */ 18473, /* Talon King Ikiss - Sethekk Halls Normal */ 20706, /* Talon King Ikiss - Sethekk Halls Heroic */ 17798, /* Warlord Kalithresh - The Steamvault Normal */ 20633, /* Warlord Kalithresh - The Steamvault Heroic */ 18708, /* Murmur - Shadow Labyrinth Normal */ 20657, /* Murmur - Shadow Labyrinth Heroic */ 16808, /* Warchief Kargath Bladefist - The Shattered Halls Normal */ 20597, /* Warchief Kargath Bladefist - The Shattered Halls Heroic */ 17881, /* Aeonus - The Black Morass Normal */ 20737, /* Aeonus - The Black Morass Heroic */ 17977, /* Warp Splinter - The Botanica Normal */ 21582, /* Warp Splinter - The Botanica Heroic */ 19220, /* Pathaleon the Calculator - The Mechanar Normal */ 21537, /* Pathaleon the Calculator - The Mechanar Heroic */ 20912, /* Harbinger Skyriss - The Arcatraz Normal */ 21601, /* Harbinger Skyriss - The Arcatraz Heroic */ 19622, /* Kael'thas Sunstrider - Magisters' Terrace Normal */ 24857, /* Kael'thas Sunstrider - Magisters' Terrace Heroic */ // --- Burning Crusade Raids --- 15690, /* Prince Malchezaar - Karazhan */ 23863, /* Zul'jin - Zul'Aman */ 19044, /* Gruul the Dragonkiller - Gruul's Lair */ 17257, /* Magtheridon - Magtheridon's Lair */ 21212, /* Lady Vashj - Serpentshrine Cavern */ 24664, /* Kael'thas Sunstrider - The Eye */ 24855, /* Kael'thas Sunstrider - The Eye */ 17968, /* Archimonde - Hyjal Summit */ 22917, /* Illidan Stormrage - Black Temple */ 25315, /* Kil'jaeden - Sunwell Plateau */ // --- Wrath of the Lich King --- 23954, /* Ingvar the Plunderer - Utgarde Keep Normal */ 31673, /* Ingvar the Plunderer - Utgarde Keep Heroic */ 26861, /* King Ymiron - Utgarde Pinnacle Normal */ 30788, /* King Ymiron - Utgarde Pinnacle Heroic */ 26723, /* Keristrasza - The Nexus Normal */ 30540, /* Keristrasza - The Nexus Heroic */ 26632, /* The Prophet Tharon'ja - Drak'Tharon Keep Normal */ 31360, /* The Prophet Tharon'ja - Drak'Tharon Keep Heroic */ 27656, /* Ley-Guardian Eregos - The Oculus Normal */ 31561, /* Ley-Guardian Eregos - The Oculus Heroic */ 29311, /* Herald Volazj - Ahn'kahet: The Old Kingdom Normal */ 31464, /* Herald Volazj - Ahn'kahet: The Old Kingdom Heroic */ 29120, /* Anub'arak - Azjol-Nerub Normal */ 31610, /* Anub'arak - Azjol-Nerub Heroic */ 29306, /* Gal'darah - Gundrak Normal */ 31368, /* Gal'darah - Gundrak Heroic */ 26533, /* Mal'Ganis - Culling of Stratholme Normal */ 31217, /* Mal'Ganis - Culling of Stratholme Heroic */ 31134, /* Cyanigosa - Violet Hold Normal */ 31506, /* Cyanigosa - Violet Hold Heroic */ 27978, /* Sjonnir the Ironshaper - Halls of Stone Normal */ 31386, /* Sjonnir the Ironshaper - Halls of Stone Heroic */ 28923, /* Loken - Halls of Lightning Normal */ 31538, /* Loken - Halls of Lightning Heroic */ 35451, /* The Black Knight - Trial of the Champion Normal */ 35490, /* The Black Knight - Trial of the Champion Heroic */ 36502, /* Devourer of Souls - The Forge of Souls Normal */ 37677, /* Devourer of Souls - The Forge of Souls Heroic */ 36658, /* Scourgelord Tyrannus - Pit of Saron Normal */ 36938, /* Scourgelord Tyrannus - Pit of Saron Heroic */ 37226, /* The Lich King Encounter - Halls of Reflection Normal */ 39166, /* The Lich King Encounter - Halls of Reflection Heroic */ // --- Wrath of the Lich King Raids --- 15990, /* Kel'Thuzad - Naxxramas */ 28859, /* Malygos - The Eye of Eternity */ 28860, /* Sartharion - Obsidian Sanctum */ 31125, /* Archavon the Stone Watcher - Vault of Archavon */ 33993, /* Emalon the Storm Watcher - Vault of Archavon */ 35013, /* Koralon the Flame Watcher - Vault of Archavon */ 38433, /* Toravon the Ice Watcher - Vault of Archavon */ 33288, /* Yogg-Saron - Ulduar */ 34564, /* Anub'arak - Trial of the Crusader */ 10184, /* Onyxia - Onyxia's Lair (re-released in Wrath) */ 36597, /* The Lich King - Icecrown Citadel */ 39863, /* Halion - Ruby Sanctum */ }; return std::find(finalBosses.begin(), finalBosses.end(), creature->GetEntry()) != finalBosses.end(); } /** * Function is copied because was not accessible in core creature class */ float GetTypeHealthModifier(int32 Rank) { switch (Rank) { case CREATURE_ELITE_NORMAL: return sWorld->getRate(RATE_CREATURE_NORMAL_HP); case CREATURE_ELITE_ELITE: return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP); case CREATURE_ELITE_RAREELITE: return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_HP); case CREATURE_ELITE_WORLDBOSS: return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_HP); case CREATURE_ELITE_RARE: return sWorld->getRate(RATE_CREATURE_ELITE_RARE_HP); default: return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP); } } // This takes the orignal health and scales flat based on the factor then applies the configuration modifier from the conf file uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod) { // int32 rank = 0; if(cInfo && cInfo->rank > 0) { rank = cInfo->rank; } // These Factors that increase or decrease health based on different settings applied to the creature // Health Variation is used to create some random element to HP so not all creatures of the same level // have the same HP for more variety. float healthVariation; // This is the fine grained hpScaleFactor set for the instance (and/or) creature overrides in the database. 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.50; } // Add in special overrides here as necessary: if(creature->GetEntry() == HEADLESS_HORSEMAN) { healthVariation = frand(1.0f, 1.1f); } float unitTypeMod = GetTypeHealthModifier(rank); 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->IsRaidOrHeroicDungeon()) { basehp *= 1.25f; } /** * @brief Calculating the final creature health encompasses all the potential modifiers * CreatureTemplate.HealthModifier (ModHealth) - Creatures that are capable of being in a Heroic instance get a boost here * even though they are the same. In this case we allow * * hpScaleFactor: allows to tweak the bonus modifier more directly at a creature or instance level, since * you can not override it in cInfo directly as it is loaded statically from database * * confHPMod: is from the mythic settings directly. */ if(cInfo->ModHealth > 0.0f) { return uint32(basehp * (cInfo->ModHealth + hpScaleFactor) * confHPMod); } else { return uint32(basehp * hpScaleFactor * confHPMod); } } // Calculates a balanced growth curve that provides good scaling across all level ranges float CalculateScaling(int levelDifference, float scaleFactor, float constant, float /*growthFactor*/) { float levelMultiplier; if (levelDifference <= 0) { // High-level creatures need a minimum boost levelMultiplier = 1.5f; // Minimum 50% boost for near-level or higher creatures } else if (levelDifference <= 10) { // Moderate scaling for small level differences levelMultiplier = 1.5f + (levelDifference * 0.2f); // 1.5x to 3.5x } else if (levelDifference <= 30) { // Higher scaling for medium level differences levelMultiplier = 3.5f + ((levelDifference - 10) * 0.15f); // 3.5x to 6.5x } else { // Cap extreme scaling for very low level creatures levelMultiplier = 6.5f + std::min((levelDifference - 30) * 0.05f, 3.5f); // Cap at 10x } float scaling = constant * levelMultiplier * (1 + (scaleFactor / 100.0f)); return scaling; } float GetTypeDamageModifier(int32 Rank) { switch (Rank) { case CREATURE_ELITE_NORMAL: return sWorld->getRate(RATE_CREATURE_NORMAL_DAMAGE); case CREATURE_ELITE_ELITE: return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE); case CREATURE_ELITE_RAREELITE: return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_DAMAGE); case CREATURE_ELITE_WORLDBOSS: return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE); case CREATURE_ELITE_RARE: return sWorld->getRate(RATE_CREATURE_ELITE_RARE_DAMAGE); default: return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE); } }