#include "ABAllCreatureScript.h" #include "ABConfig.h" #include "ABCreatureInfo.h" #include "ABMapInfo.h" #include "ABScriptMgr.h" #include "ABUtils.h" #include "AutoBalance.h" #include "MapMgr.h" void AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel(const CreatureTemplate* /*creatureTemplate*/, Creature* creature, uint8& level) { Map* creatureMap = creature->GetMap(); if (creatureMap && creatureMap->IsDungeon()) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel: Creature {} ({}) | Entry ID: ({}) | Spawn ID: ({})", creature->GetName(), level, creature->GetEntry(), creature->GetSpawnId() ); // Create the new creature's AB info AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // mark this creature as brand new so that only the level will be modified before creation creatureABInfo->isBrandNew = true; // if the creature already has a selectedLevel on it, we have already processed it and can re-use that value if (creatureABInfo->selectedLevel) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel: Creature {} ({}) | has already been processed, using level {}.", creature->GetName(), creatureABInfo->UnmodifiedLevel, creatureABInfo->selectedLevel ); level = creatureABInfo->selectedLevel; return; } // Update the map's data if it is out of date (just before changing the map's creature list) UpdateMapDataIfNeeded(creature->GetMap()); Map* creatureMap = creature->GetMap(); InstanceMap* instanceMap = creatureMap->ToInstanceMap(); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel: Creature {} ({}) | is in map {} ({}{}{}{})", creature->GetName(), level, creatureMap->GetMapName(), creatureMap->GetId(), instanceMap ? "-" + std::to_string(instanceMap->GetInstanceId()) : "", instanceMap ? ", " + std::to_string(instanceMap->GetMaxPlayers()) + "-player" : "", instanceMap ? instanceMap->IsHeroic() ? " Heroic" : " Normal" : "" ); // Set level originally intended for the creature creatureABInfo->UnmodifiedLevel = level; // add the creature to the map's tracking list AddCreatureToMapCreatureList(creature); // Update the map's data if it is out of date (just after changing the map's creature list) UpdateMapDataIfNeeded(creature->GetMap()); // do an initial modification run of the creature, but don't update the level yet ModifyCreatureAttributes(creature); if (isCreatureRelevant(creature)) { // set the new creature level level = creatureABInfo->selectedLevel; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel: Creature {} ({}) | will spawn in as level ({}).", creature->GetName(), creatureABInfo->UnmodifiedLevel, creatureABInfo->selectedLevel ); } else { // don't change level value LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnBeforeCreatureSelectLevel: Creature {} ({}) | will spawn in at its original level ({}).", creature->GetName(), creatureABInfo->UnmodifiedLevel, creatureABInfo->selectedLevel ); } } } void AutoBalance_AllCreatureScript::OnCreatureSelectLevel(const CreatureTemplate* /* cinfo */, Creature* creature) { // ensure we're in a dungeon with a creature if ( !creature || !creature->GetMap() || !creature->GetMap()->IsDungeon() || !creature->GetMap()->GetInstanceId() ) { return; } // get the creature's info AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // If the creature is brand new, it needs more processing if (creatureABInfo->isBrandNew) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureSelectLevel: Creature {} ({}) | Entry ID: ({}) | Spawn ID: ({})", creature->GetName(), creature->GetLevel(), creature->GetEntry(), creature->GetSpawnId() ); if (creatureABInfo->isBrandNew) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureSelectLevel: Creature {} ({}) | is no longer brand new.", creature->GetName(), creature->GetLevel() ); creatureABInfo->isBrandNew = false; } // Update the map's data if it is out of date UpdateMapDataIfNeeded(creature->GetMap()); ModifyCreatureAttributes(creature); // store the creature's max health value for validation in `OnCreatureAddWorld` creatureABInfo->initialMaxHealth = creature->GetMaxHealth(); AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); if (creature->GetLevel() != creatureABInfo->selectedLevel && isCreatureRelevant(creature)) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureSelectLevel: Creature {} ({}) | is set to level ({}).", creature->GetName(), creature->GetLevel(), creatureABInfo->selectedLevel ); creature->SetLevel(creatureABInfo->selectedLevel); } } else { LOG_ERROR("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureSelectLevel: Creature {} ({}) | is new to the instance but wasn't flagged as brand new. Please open an issue.", creature->GetName(), creature->GetLevel() ); } } void AutoBalance_AllCreatureScript::OnCreatureAddWorld(Creature* creature) { if (creature->GetMap()->IsDungeon()) { Map* creatureMap = creature->GetMap(); InstanceMap* instanceMap = creatureMap->ToInstanceMap(); AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // final checks on the creature before spawning if (isCreatureRelevant(creature)) { // level check if (creature->GetLevel() != creatureABInfo->selectedLevel && !creature->IsSummon()) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureAddWorld: Creature {} ({}) | is set to level ({}) just after being added to the world.", creature->GetName(), creature->GetLevel(), creatureABInfo->selectedLevel ); creature->SetLevel(creatureABInfo->selectedLevel); } // max health check if (creature->GetMaxHealth() != creatureABInfo->initialMaxHealth) { float oldMaxHealth = creature->GetMaxHealth(); float healthPct = creature->GetHealthPct(); creature->SetMaxHealth(creatureABInfo->initialMaxHealth); creature->SetHealth(creature->GetMaxHealth() * (healthPct / 100)); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureAddWorld: Creature {} ({}) | had its max health changed from ({})->({}) just after being added to the world.", creature->GetName(), creature->GetLevel(), oldMaxHealth, creatureABInfo->initialMaxHealth ); } } LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureAddWorld: Creature {} ({}) | added to map {} ({}{}{}{})", creature->GetName(), creature->GetLevel(), creatureMap->GetMapName(), creatureMap->GetId(), instanceMap ? "-" + std::to_string(instanceMap->GetInstanceId()) : "", instanceMap ? ", " + std::to_string(instanceMap->GetMaxPlayers()) + "-player" : "", instanceMap ? instanceMap->IsHeroic() ? " Heroic" : " Normal" : "" ); } } void AutoBalance_AllCreatureScript::OnCreatureRemoveWorld(Creature* creature) { if (creature->GetMap()->IsDungeon()) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureRemoveWorld: Creature {} ({}) | Entry ID: ({}) | Spawn ID: ({})", creature->GetName(), creature->GetLevel(), creature->GetEntry(), creature->GetSpawnId() ); InstanceMap* instanceMap = creature->GetMap()->ToInstanceMap(); Map* map = sMapMgr->FindBaseMap(creature->GetMapId()); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureRemoveWorld: Creature {} ({}) | removed from map {} ({}{}{}{})", creature->GetName(), creature->GetLevel(), map->GetMapName(), map->GetId(), instanceMap ? "-" + std::to_string(instanceMap->GetInstanceId()) : "", instanceMap ? ", " + std::to_string(instanceMap->GetMaxPlayers()) + "-player" : "", instanceMap ? instanceMap->IsHeroic() ? " Heroic" : " Normal" : "" ); // remove the creature from the map's tracking list, if present RemoveCreatureFromMapData(creature); } } void AutoBalance_AllCreatureScript::OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) { // ensure we're in a dungeon with a creature if ( !creature || !creature->GetMap() || !creature->GetMap()->IsDungeon() || !creature->GetMap()->GetInstanceId() ) { return; } // update map data before making creature changes UpdateMapDataIfNeeded(creature->GetMap()); // If the config is out of date and the creature was reset, run modify against it if (ResetCreatureIfNeeded(creature)) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnAllCreatureUpdate: Creature {} ({}) | Entry ID: ({}) | Spawn ID: ({})", creature->GetName(), creature->GetLevel(), creature->GetEntry(), creature->GetSpawnId() ); // Update the map's data if it is out of date UpdateMapDataIfNeeded(creature->GetMap()); ModifyCreatureAttributes(creature); AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); if (creature->GetLevel() != creatureABInfo->selectedLevel && isCreatureRelevant(creature)) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnAllCreatureUpdate: Creature {} ({}) | is set to level ({}).", creature->GetName(), creature->GetLevel(), creatureABInfo->selectedLevel ); creature->SetLevel(creatureABInfo->selectedLevel); } } } // Reset the passed creature to stock if the config has changed bool AutoBalance_AllCreatureScript::ResetCreatureIfNeeded(Creature* creature) { // make sure we have a creature if (!creature || !isCreatureRelevant(creature)) return false; // get (or create) map and creature info AutoBalanceMapInfo* mapABInfo = GetMapInfo(creature->GetMap()); AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // if creature is dead and mapConfigTime is 0, skip for now if (creature->isDead() && creatureABInfo->mapConfigTime == 1) return false; // if the creature is dead but mapConfigTime is NOT 0, we set it to 0 so that it will be recalculated if revived // also remember that this creature was once alive but is now dead else if (creature->isDead()) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) | is dead and mapConfigTime is not 0 - prime for reset if revived.", creature->GetName(), creature->GetLevel()); creatureABInfo->mapConfigTime = 1; creatureABInfo->wasAliveNowDead = true; return false; } // if the config is outdated, reset the creature if (creatureABInfo->mapConfigTime < mapABInfo->mapConfigTime) { LOG_DEBUG("module.AutoBalance", "AutoBalance:: ------------------------------------------------"); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) | Entry ID: ({}) | Spawn ID: ({})", creature->GetName(), creature->GetLevel(), creature->GetEntry(), creature->GetSpawnId() ); LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) | Map config time is out of date ({} < {}). Resetting creature before modify.", creature->GetName(), creature->GetLevel(), creatureABInfo->mapConfigTime, mapABInfo->mapConfigTime ); // retain some values uint8 unmodifiedLevel = creatureABInfo->UnmodifiedLevel; bool isActive = creatureABInfo->isActive; bool wasAliveNowDead = creatureABInfo->wasAliveNowDead; bool isInCreatureList = creatureABInfo->isInCreatureList; // reset AutoBalance modifiers creature->CustomData.Erase("AutoBalanceCreatureInfo"); AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // grab the creature's template and the original creature's stats CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate(); // set the creature's level if (creature->GetLevel() != unmodifiedLevel) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) | is set to level ({}).", creature->GetName(), creature->GetLevel(), unmodifiedLevel ); creature->SetLevel(unmodifiedLevel); creature->UpdateAllStats(); } else { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) | is already set to level ({}).", creature->GetName(), creature->GetLevel(), unmodifiedLevel ); } // get the creature's base stats CreatureBaseStats const* origCreatureBaseStats = sObjectMgr->GetCreatureBaseStats(unmodifiedLevel, creatureTemplate->unit_class); // health float currentHealthPercent = (float)creature->GetHealth() / (float)creature->GetMaxHealth(); creature->SetMaxHealth(origCreatureBaseStats->GenerateHealth(creatureTemplate)); creature->SetHealth((float)origCreatureBaseStats->GenerateHealth(creatureTemplate) * currentHealthPercent); // mana if (creature->getPowerType() == POWER_MANA && creature->GetPower(POWER_MANA) >= 0 && creature->GetMaxPower(POWER_MANA) > 0) { float currentManaPercent = creature->GetPower(POWER_MANA) / creature->GetMaxPower(POWER_MANA); creature->SetMaxPower(POWER_MANA, origCreatureBaseStats->GenerateMana(creatureTemplate)); creature->SetPower(POWER_MANA, creature->GetMaxPower(POWER_MANA) * currentManaPercent); } // armor creature->SetArmor(origCreatureBaseStats->GenerateArmor(creatureTemplate)); // restore the saved data creatureABInfo->UnmodifiedLevel = unmodifiedLevel; creatureABInfo->isActive = isActive; creatureABInfo->wasAliveNowDead = wasAliveNowDead; creatureABInfo->isInCreatureList = isInCreatureList; // damage and ccduration are handled using AutoBalanceCreatureInfo data only LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded: Creature {} ({}) is reset to its original stats.", creature->GetName(), creature->GetLevel() ); // return true to indicate that the creature was reset return true; } // creature was not reset, return false return false; } void AutoBalance_AllCreatureScript::ModifyCreatureAttributes(Creature* creature) { // make sure we have a creature if (!creature) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: creature is null."); return; } // grab creature and map data AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); Map* map = creature->GetMap(); InstanceMap* instanceMap = map->ToInstanceMap(); AutoBalanceMapInfo* mapABInfo = GetMapInfo(instanceMap); // mark the creature as updated using the current settings if needed // if this creature is brand new, do not update this so that it will be re-processed next OnCreatureUpdate if (creatureABInfo->mapConfigTime < mapABInfo->mapConfigTime && !creatureABInfo->isBrandNew) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Map config time set to ({}).", creature->GetName(), creature->GetLevel(), mapABInfo->mapConfigTime ); creatureABInfo->mapConfigTime = mapABInfo->mapConfigTime; } // check to make sure that the creature's map is enabled for scaling if (!mapABInfo->enabled) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is in map {} ({}{}{}{}) that is not enabled, not changed.", creature->GetName(), creatureABInfo->UnmodifiedLevel, map->GetMapName(), map->GetId(), instanceMap ? "-" + std::to_string(instanceMap->GetInstanceId()) : "", instanceMap ? ", " + std::to_string(instanceMap->GetMaxPlayers()) + "-player" : "", instanceMap ? instanceMap->IsHeroic() ? " Heroic" : " Normal" : "" ); // return the creature back to their original level, if it's not already creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; return; } // if the creature isn't relevant, don't modify it if (!isCreatureRelevant(creature)) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is not relevant, not changed.", creature->GetName(), creatureABInfo->UnmodifiedLevel ); // return the creature back to their original level, if it's not already creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; return; } // if this creature is below 85% of the minimum LFG level for the map, make no changes // if this creature is above 115% of the maximum LFG level for the map, make no changes // if this is a critter that is substantial enough to be considered a real enemy, still modify it // if this is a trigger, still modify it if ( ( (creatureABInfo->UnmodifiedLevel < (uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f)) || (creatureABInfo->UnmodifiedLevel > (uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f)) ) && ( !(creature->IsCritter() && creatureABInfo->UnmodifiedLevel >= 5 && creature->GetMaxHealth() > 100) && !creature->IsTrigger() ) ) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is a {} outside of the expected NPC level range for the map ({} to {}), not modified.", creature->GetName(), creatureABInfo->UnmodifiedLevel, creature->IsCritter() ? "critter" : "creature", (uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f), (uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f) ); creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; return; } // if the creature was dead (but this function is being called because they are being revived), reset it and allow modifications if (creatureABInfo->wasAliveNowDead) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | was dead but appears to be alive now, reset wasAliveNowDead flag.", creature->GetName(), creatureABInfo->UnmodifiedLevel); // if the creature was dead, reset it creatureABInfo->wasAliveNowDead = false; } // if the creature is dead and wasn't marked as dead by this script, simply skip else if (creature->isDead()) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is dead, do not modify.", creature->GetName(), creatureABInfo->UnmodifiedLevel); return; } CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate(); // check to see if the creature is in the forced num players list uint32 forcedNumPlayers = GetForcedNumPlayers(creatureTemplate->Entry); if (forcedNumPlayers == 0) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is in the forced num players list with a value of 0, not changed.", creature->GetName(), creatureABInfo->UnmodifiedLevel); return; // forcedNumPlayers 0 means that the creature is contained in DisabledID -> no scaling } // start with the map's adjusted player count uint32 adjustedPlayerCount = mapABInfo->adjustedPlayerCount; // if the forced value is set and the adjusted player count is above the forced value, change it to match if (forcedNumPlayers > 0 && adjustedPlayerCount > forcedNumPlayers) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is in the forced num players list with a value of {}, adjusting adjustedPlayerCount to match.", creature->GetName(), creatureABInfo->UnmodifiedLevel, forcedNumPlayers); adjustedPlayerCount = forcedNumPlayers; } // store the current player count in the creature and map's data creatureABInfo->instancePlayerCount = adjustedPlayerCount; if (!creatureABInfo->instancePlayerCount) // no players in map, do not modify attributes { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is on a map with no players, not changed.", creature->GetName(), creatureABInfo->UnmodifiedLevel); return; } if (!sABScriptMgr->OnBeforeModifyAttributes(creature, creatureABInfo->instancePlayerCount)) return; // only scale levels if level scaling is enabled and the instance's average creature level is not within the skip range if ( LevelScaling && ( (mapABInfo->avgCreatureLevel > mapABInfo->highestPlayerLevel + mapABInfo->levelScalingSkipHigherLevels || mapABInfo->levelScalingSkipHigherLevels == 0) || (mapABInfo->avgCreatureLevel < mapABInfo->highestPlayerLevel - mapABInfo->levelScalingSkipLowerLevels || mapABInfo->levelScalingSkipLowerLevels == 0) ) && !creatureABInfo->neverLevelScale ) { uint8 selectedLevel; // handle "special" creatures // note that these already passed a more complex check above if ( (creature->IsTotem() && creature->IsSummon() && creatureABInfo->summoner && creatureABInfo->summoner->IsPlayer()) || ( creature->IsCritter() && creatureABInfo->UnmodifiedLevel <= 5 && creature->GetMaxHealth() <= 100 ) ) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is a {} that will not be level scaled, but will have modifiers set.", creature->GetName(), creatureABInfo->UnmodifiedLevel, creature->IsTotem() ? "totem" : "critter" ); selectedLevel = creatureABInfo->UnmodifiedLevel; } // if we're using dynamic scaling, calculate the creature's level based relative to the highest player level in the map else if (LevelScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) { // calculate the creature's new level selectedLevel = (mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling) - (mapABInfo->highestCreatureLevel - creatureABInfo->UnmodifiedLevel); // check to be sure that the creature's new level is at least the dynamic scaling floor if (selectedLevel < (mapABInfo->highestPlayerLevel - mapABInfo->levelScalingDynamicFloor)) selectedLevel = mapABInfo->highestPlayerLevel - mapABInfo->levelScalingDynamicFloor; // check to be sure that the creature's new level is no higher than the dynamic scaling ceiling if (selectedLevel > (mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling)) selectedLevel = mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaled to level ({}) via dynamic scaling.", creature->GetName(), creatureABInfo->UnmodifiedLevel, selectedLevel ); } // otherwise we're using "fixed" scaling and should use the highest player level in the map else { selectedLevel = mapABInfo->highestPlayerLevel; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaled to level ({}) via fixed scaling.", creature->GetName(), creatureABInfo->UnmodifiedLevel, selectedLevel); } creatureABInfo->selectedLevel = selectedLevel; if (creature->GetLevel() != selectedLevel) { if (!creatureABInfo->isBrandNew) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is set to new selectedLevel ({}).", creature->GetName(), creatureABInfo->UnmodifiedLevel, selectedLevel ); creature->SetLevel(selectedLevel); } } } else if (!LevelScaling) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | not level scaled due to level scaling being disabled.", creature->GetName(), creatureABInfo->UnmodifiedLevel); creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; } else if (creatureABInfo->neverLevelScale) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | not level scaled due to being marked as multipliers only.", creature->GetName(), creatureABInfo->UnmodifiedLevel); creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; } else { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | not level scaled due the instance's average creature level being inside the skip range.", creature->GetName(), creatureABInfo->UnmodifiedLevel); creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; } if (creatureABInfo->isBrandNew) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | is brand new, do not modify level or stats yet.", creature->GetName(), creatureABInfo->UnmodifiedLevel ); return; } CreatureBaseStats const* origCreatureBaseStats = sObjectMgr->GetCreatureBaseStats(creatureABInfo->UnmodifiedLevel, creatureTemplate->unit_class); CreatureBaseStats const* newCreatureBaseStats = sObjectMgr->GetCreatureBaseStats(creatureABInfo->selectedLevel, creatureTemplate->unit_class); // Inflection Point AutoBalanceInflectionPointSettings inflectionPointSettings = getInflectionPointSettings(instanceMap, isBossOrBossSummon(creature)); // Generate the default multiplier float defaultMultiplier = getDefaultMultiplier(instanceMap, inflectionPointSettings); if (!sABScriptMgr->OnAfterDefaultMultiplier(creature, defaultMultiplier)) return; // Stat Modifiers AutoBalanceStatModifiers statModifiers = getStatModifiers(map, creature); float statMod_global = statModifiers.global; float statMod_health = statModifiers.health; float statMod_mana = statModifiers.mana; float statMod_armor = statModifiers.armor; float statMod_damage = statModifiers.damage; float statMod_ccDuration = statModifiers.ccduration; // Storage for the final values applied to the creature uint32 newFinalHealth = 0; uint32 newFinalMana = 0; uint32 newFinalArmor = 0; // // Health Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- HEALTH MULTIPLIER ----------", creature->GetName(), creatureABInfo->selectedLevel ); float healthMultiplier = defaultMultiplier * statMod_global * statMod_health; float scaledHealthMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | HealthMultiplier: ({}) = defaultMultiplier ({}) * statMod_global ({}) * statMod_health ({})", creature->GetName(), creatureABInfo->selectedLevel, healthMultiplier, defaultMultiplier, statMod_global, statMod_health ); // Can't be less than MinHPModifier if (healthMultiplier <= MinHPModifier) { healthMultiplier = MinHPModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | HealthMultiplier: ({}) - capped to MinHPModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, healthMultiplier, MinHPModifier ); } // set the non-level-scaled health multiplier on the creature's AB info creatureABInfo->HealthMultiplier = healthMultiplier; // only level scale health if level scaling is enabled and the creature level has been altered if (LevelScaling && creatureABInfo->selectedLevel != creatureABInfo->UnmodifiedLevel) { // the max health that the creature had before we did anything with it float origHealth = origCreatureBaseStats->GenerateHealth(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origHealth ({}) = origCreatureBaseStats->GenerateHealth(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origHealth ); // the base health of the new creature level for this creature's class // uses a custom smoothing formula to smooth transitions between expansions float newBaseHealth = getBaseExpansionValueForLevel(newCreatureBaseStats->BaseHealth, mapABInfo->highestPlayerLevel); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newBaseHealth ({}) = getBaseExpansionValueForLevel(newCreatureBaseStats->BaseHealth, mapABInfo->highestPlayerLevel ({}))", creature->GetName(), creatureABInfo->selectedLevel, newBaseHealth, mapABInfo->highestPlayerLevel ); // the health of the creature at its new level (before per-player scaling) float newHealth = newBaseHealth * creatureTemplate->ModHealth; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newHealth ({}) = newBaseHealth ({}) * creature ModHealth ({})", creature->GetName(), creatureABInfo->selectedLevel, newHealth, newBaseHealth, creatureTemplate->ModHealth ); // the multiplier that would need to be applied to the creature's original health to get the new level's health (before per-player scaling) float newHealthMultiplier = newHealth / origHealth; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newHealthMultiplier ({}) = newHealth ({}) / origHealth ({})", creature->GetName(), creatureABInfo->selectedLevel, newHealthMultiplier, newHealth, origHealth ); // the multiplier that would need to be applied to the creature's original health to get the new level's health (after per-player scaling) scaledHealthMultiplier = healthMultiplier * newHealthMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledHealthMultiplier ({}) = healthMultiplier ({}) * newHealthMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledHealthMultiplier, healthMultiplier, newHealthMultiplier ); // the actual health value to be applied to the level-scaled and player-scaled creature newFinalHealth = round(origHealth * scaledHealthMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalHealth ({}) = origHealth ({}) * scaledHealthMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalHealth, origHealth, scaledHealthMultiplier ); } else { // the non-level-scaled health multiplier is the same as the level-scaled health multiplier scaledHealthMultiplier = healthMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledHealthMultiplier ({}) = healthMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledHealthMultiplier, healthMultiplier ); // the original health of the creature uint32 origHealth = origCreatureBaseStats->GenerateHealth(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origHealth ({}) = origCreatureBaseStats->GenerateHealth(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origHealth ); // the actual health value to be applied to the player-scaled creature newFinalHealth = round(origHealth * creatureABInfo->HealthMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalHealth ({}) = origHealth ({}) * HealthMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalHealth, origHealth, creatureABInfo->HealthMultiplier ); } // // Mana Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- MANA MULTIPLIER ----------", creature->GetName(), creatureABInfo->selectedLevel ); float manaMultiplier = defaultMultiplier * statMod_global * statMod_mana; float scaledManaMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ManaMultiplier: ({}) = defaultMultiplier ({}) * statMod_global ({}) * statMod_mana ({})", creature->GetName(), creatureABInfo->selectedLevel, manaMultiplier, defaultMultiplier, statMod_global, statMod_mana ); // Can't be less than MinManaModifier if (manaMultiplier <= MinManaModifier) { manaMultiplier = MinManaModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ManaMultiplier: ({}) - capped to MinManaModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, manaMultiplier, MinManaModifier ); } // if the creature doesn't have mana, set the multiplier to 0.0 if (!origCreatureBaseStats->GenerateMana(creatureTemplate)) { manaMultiplier = 0.0f; creatureABInfo->ManaMultiplier = 0.0f; scaledManaMultiplier = 0.0f; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Creature doesn't have mana, multiplier set to ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->ManaMultiplier ); } // if the creature has mana, continue calculations else { // set the non-level-scaled mana multiplier on the creature's AB info creatureABInfo->ManaMultiplier = manaMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ManaMultiplier: ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->ManaMultiplier ); // only level scale mana if level scaling is enabled and the creature level has been altered if (LevelScaling && creatureABInfo->selectedLevel != creatureABInfo->UnmodifiedLevel) { // the max mana that the creature had before we did anything with it uint32 origMana = origCreatureBaseStats->GenerateMana(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origMana ({}) = origCreatureBaseStats->GenerateMana(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origMana ); // the max mana that the creature would have at its new level // there is no per-expansion adjustment for mana uint32 newMana = newCreatureBaseStats->GenerateMana(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newMana ({}) = newCreatureBaseStats->GenerateMana(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, newMana ); // the multiplier that would need to be applied to the creature's original mana to get the new level's mana (before per-player scaling) float newManaMultiplier = (float)newMana / (float)origMana; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newManaMultiplier ({}) = newMana ({}) / origMana ({})", creature->GetName(), creatureABInfo->selectedLevel, newManaMultiplier, newMana, origMana ); // the multiplier that would need to be applied to the creature's original mana to get the new level's mana (after per-player scaling) scaledManaMultiplier = manaMultiplier * newManaMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledManaMultiplier ({}) = manaMultiplier ({}) * newManaMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledManaMultiplier, manaMultiplier, newManaMultiplier ); // the actual mana value to be applied to the level-scaled and player-scaled creature newFinalMana = round(origMana * scaledManaMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalMana ({}) = origMana ({}) * scaledManaMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalMana, origMana, scaledManaMultiplier ); } else { // scaled mana multiplier is the same as the non-level-scaled mana multiplier scaledManaMultiplier = manaMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledManaMultiplier ({}) = manaMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledManaMultiplier, manaMultiplier ); // the original mana of the creature uint32 origMana = origCreatureBaseStats->GenerateMana(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origMana ({}) = origCreatureBaseStats->GenerateMana(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origMana ); // the actual mana value to be applied to the player-scaled creature newFinalMana = round(origMana * creatureABInfo->ManaMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalMana ({}) = origMana ({}) * creatureABInfo->ManaMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalMana, origMana, creatureABInfo->ManaMultiplier ); } } // // Armor Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- ARMOR MULTIPLIER ----------", creature->GetName(), creatureABInfo->selectedLevel ); float armorMultiplier = defaultMultiplier * statMod_global * statMod_armor; float scaledArmorMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | armorMultiplier: ({}) = defaultMultiplier ({}) * statMod_global ({}) * statMod_armor ({})", creature->GetName(), creatureABInfo->selectedLevel, armorMultiplier, defaultMultiplier, statMod_global, statMod_armor ); // set the non-level-scaled armor multiplier on the creature's AB info creatureABInfo->ArmorMultiplier = armorMultiplier; // only level scale armor if level scaling is enabled and the creature level has been altered if (LevelScaling && creatureABInfo->selectedLevel != creatureABInfo->UnmodifiedLevel) { // the armor that the creature had before we did anything with it uint32 origArmor = origCreatureBaseStats->GenerateArmor(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origArmor ({}) = origCreatureBaseStats->GenerateArmor(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origArmor ); // the armor that the creature would have at its new level // there is no per-expansion adjustment for armor uint32 newArmor = newCreatureBaseStats->GenerateArmor(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newArmor ({}) = newCreatureBaseStats->GenerateArmor(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, newArmor ); // the multiplier that would need to be applied to the creature's original armor to get the new level's armor (before per-player scaling) float newArmorMultiplier = (float)newArmor / (float)origArmor; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newArmorMultiplier ({}) = newArmor ({}) / origArmor ({})", creature->GetName(), creatureABInfo->selectedLevel, newArmorMultiplier, newArmor, origArmor ); // the multiplier that would need to be applied to the creature's original armor to get the new level's armor (after per-player scaling) scaledArmorMultiplier = armorMultiplier * newArmorMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledArmorMultiplier ({}) = armorMultiplier ({}) * newArmorMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledArmorMultiplier, armorMultiplier, newArmorMultiplier ); // the actual armor value to be applied to the level-scaled and player-scaled creature newFinalArmor = round(origArmor * scaledArmorMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalArmor ({}) = origArmor ({}) * scaledArmorMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalArmor, origArmor, scaledArmorMultiplier ); } else { // Scaled armor multiplier is the same as the non-level-scaled armor multiplier scaledArmorMultiplier = armorMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledArmorMultiplier ({}) = armorMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledArmorMultiplier, armorMultiplier ); // the original armor of the creature uint32 origArmor = origCreatureBaseStats->GenerateArmor(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origArmor ({}) = origCreatureBaseStats->GenerateArmor(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origArmor ); // the actual armor value to be applied to the player-scaled creature newFinalArmor = round(origArmor * creatureABInfo->ArmorMultiplier); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newFinalArmor ({}) = origArmor ({}) * creatureABInfo->ArmorMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, newFinalArmor, origArmor, creatureABInfo->ArmorMultiplier ); } // // Damage Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- DAMAGE MULTIPLIER ----------", creature->GetName(), creatureABInfo->selectedLevel ); float damageMultiplier = defaultMultiplier * statMod_global * statMod_damage; float scaledDamageMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | DamageMultiplier: ({}) = defaultMultiplier ({}) * statMod_global ({}) * statMod_damage ({})", creature->GetName(), creatureABInfo->selectedLevel, damageMultiplier, defaultMultiplier, statMod_global, statMod_damage ); // Can't be less than MinDamageModifier if (damageMultiplier <= MinDamageModifier) { damageMultiplier = MinDamageModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | DamageMultiplier: ({}) - capped to MinDamageModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, damageMultiplier, MinDamageModifier ); } // set the non-level-scaled damage multiplier on the creature's AB info creatureABInfo->DamageMultiplier = damageMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | DamageMultiplier: ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->DamageMultiplier ); // only level scale damage if level scaling is enabled and the creature level has been altered if (LevelScaling && creatureABInfo->selectedLevel != creatureABInfo->UnmodifiedLevel) { // the original base damage of the creature // note that we don't mess with the damage modifier here since it applied equally to the original and new levels float origBaseDamage = origCreatureBaseStats->GenerateBaseDamage(creatureTemplate); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | origBaseDamage ({}) = origCreatureBaseStats->GenerateBaseDamage(creatureTemplate)", creature->GetName(), creatureABInfo->selectedLevel, origBaseDamage ); // the base damage of the new creature level for this creature's class // uses a custom smoothing formula to smooth transitions between expansions float newBaseDamage = getBaseExpansionValueForLevel(newCreatureBaseStats->BaseDamage, mapABInfo->highestPlayerLevel); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newBaseDamage ({}) = getBaseExpansionValueForLevel(newCreatureBaseStats->BaseDamage, mapABInfo->highestPlayerLevel ({}))", creature->GetName(), creatureABInfo->selectedLevel, newBaseDamage, mapABInfo->highestPlayerLevel ); // the multiplier that would need to be applied to the creature's original damage to get the new level's damage (before per-player scaling) float newDamageMultiplier = newBaseDamage / origBaseDamage; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | newDamageMultiplier ({}) = newBaseDamage ({}) / origBaseDamage ({})", creature->GetName(), creatureABInfo->selectedLevel, newDamageMultiplier, newBaseDamage, origBaseDamage ); // the actual multiplier that will be used to scale the creature's damage (after per-player scaling) scaledDamageMultiplier = damageMultiplier * newDamageMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledDamageMultiplier ({}) = damageMultiplier ({}) * newDamageMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledDamageMultiplier, damageMultiplier, newDamageMultiplier ); } else { // the scaled damage multiplier is the same as the non-level-scaled damage multiplier scaledDamageMultiplier = damageMultiplier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledDamageMultiplier ({}) = damageMultiplier ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledDamageMultiplier, origCreatureBaseStats->GenerateBaseDamage(creatureTemplate), damageMultiplier ); } // // Crowd Control Debuff Duration Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- CC DURATION MULTIPLIER ----------", creature->GetName(), creatureABInfo->selectedLevel ); float ccDurationMultiplier; if (statMod_ccDuration != -1.0f) { // calculate CC Duration from the default multiplier and the config settings ccDurationMultiplier = defaultMultiplier * statMod_ccDuration; // Min/Max checking if (ccDurationMultiplier < MinCCDurationModifier) ccDurationMultiplier = MinCCDurationModifier; else if (ccDurationMultiplier > MaxCCDurationModifier) ccDurationMultiplier = MaxCCDurationModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ccDurationMultiplier: ({})", creature->GetName(), creatureABInfo->selectedLevel, ccDurationMultiplier ); } else { // the CC Duration will not be changed ccDurationMultiplier = 1.0f; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Crowd Control Duration will not be changed.", creature->GetName(), creatureABInfo->selectedLevel ); } LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ccDurationMultiplier: ({}) = defaultMultiplier ({}) * statMod_ccDuration ({})", creature->GetName(), creatureABInfo->selectedLevel, ccDurationMultiplier, defaultMultiplier, statMod_ccDuration ); // // Apply New Values // if (!sABScriptMgr->OnBeforeUpdateStats(creature, newFinalHealth, newFinalMana, damageMultiplier, newFinalArmor)) return; uint32 prevMaxHealth = creature->GetMaxHealth(); uint32 prevMaxPower = creature->GetMaxPower(Powers::POWER_MANA); uint32 prevHealth = creature->GetHealth(); uint32 prevPower = creature->GetPower(Powers::POWER_MANA); uint32 prevPlayerDamageRequired = creature->GetPlayerDamageReq(); uint32 prevCreateHealth = creature->GetCreateHealth(); Powers pType = creature->getPowerType(); creature->SetArmor(newFinalArmor); creature->SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, (float)newFinalArmor); creature->SetCreateHealth(newFinalHealth); creature->SetMaxHealth(newFinalHealth); creature->ResetPlayerDamageReq(); creature->SetCreateMana(newFinalMana); creature->SetMaxPower(Powers::POWER_MANA, newFinalMana); creature->SetStatFlatModifier(UNIT_MOD_ENERGY, BASE_VALUE, (float)100.0f); creature->SetStatFlatModifier(UNIT_MOD_RAGE, BASE_VALUE, (float)100.0f); creature->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)newFinalHealth); creature->SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, (float)newFinalMana); creatureABInfo->ScaledHealthMultiplier = scaledHealthMultiplier; creatureABInfo->ScaledManaMultiplier = scaledManaMultiplier; creatureABInfo->ScaledArmorMultiplier = scaledArmorMultiplier; creatureABInfo->ScaledDamageMultiplier = scaledDamageMultiplier; creatureABInfo->CCDurationMultiplier = ccDurationMultiplier; // adjust the current health as appropriate uint32 scaledCurHealth = 0; uint32 scaledCurPower = 0; // if this is a summon and it's a clone of its summoner, keep the health and mana values of the summon // only do this once, when `_isSummonCloneOfSummoner(creature)` returns true but !creatureABInfo->isCloneOfSummoner is false if ( creature->IsSummon() && _isSummonCloneOfSummoner(creature) && !creatureABInfo->isCloneOfSummoner ) { creatureABInfo->isCloneOfSummoner = true; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Summon is a clone of its summoner, keeping health and mana values.", creature->GetName(), creatureABInfo->selectedLevel ); if (prevHealth && prevMaxHealth) { scaledCurHealth = prevHealth; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurHealth ({}) = prevHealth ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledCurHealth, prevHealth ); } if (prevPower && prevMaxPower) { scaledCurPower = prevPower; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurPower ({}) = prevPower ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledCurPower, prevPower ); } } else { if (prevHealth && prevMaxHealth) { scaledCurHealth = float(newFinalHealth) / float(prevMaxHealth) * float(prevHealth); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurHealth ({}) = float(newFinalHealth) ({}) / float(prevMaxHealth) ({}) * float(prevHealth) ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledCurHealth, newFinalHealth, prevMaxHealth, prevHealth ); } else { scaledCurHealth = 0; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurHealth ({}) = 0", creature->GetName(), creatureABInfo->selectedLevel, scaledCurHealth ); } if (prevPower && prevMaxPower) { scaledCurPower = float(newFinalMana) / float(prevMaxPower) * float(prevPower); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurPower ({}) = float(newFinalMana) ({}) / float(prevMaxPower) ({}) * float(prevPower) ({})", creature->GetName(), creatureABInfo->selectedLevel, scaledCurPower, newFinalMana, prevMaxPower, prevPower ); } else { scaledCurPower = 0; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | scaledCurPower ({}) = 0", creature->GetName(), creatureABInfo->selectedLevel, scaledCurPower ); } } creature->SetHealth(scaledCurHealth); if (pType == Powers::POWER_MANA) creature->SetPower(Powers::POWER_MANA, scaledCurPower); else creature->setPowerType(pType); // fix creatures with different power types uint32 playerDamageRequired = creature->GetPlayerDamageReq(); if (prevPlayerDamageRequired == 0) { // If already reached damage threshold for loot, drop to zero again creature->LowerPlayerDamageReq(playerDamageRequired, true); } else { // Scale the damage requirements similar to creature HP scaling uint32 scaledPlayerDmgReq = float(prevPlayerDamageRequired) * float(newFinalHealth) / float(prevCreateHealth); // Do some math creature->LowerPlayerDamageReq(playerDamageRequired - scaledPlayerDmgReq, true); } // // Reward Scaling // LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- REWARD SCALING ----------", creature->GetName(), creatureABInfo->selectedLevel ); // calculate the average multiplier after level scaling is applied float avgHealthDamageMultipliers; // only if one of the scaling options is enabled if (RewardScalingXP || RewardScalingMoney) { // use health and damage to calculate the average multiplier avgHealthDamageMultipliers = (scaledHealthMultiplier + scaledDamageMultiplier) / 2.0f; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | avgHealthDamageMultipliers ({}) = (scaledHealthMultiplier ({}) + scaledDamageMultiplier ({})) / 2.0f", creature->GetName(), creatureABInfo->selectedLevel, avgHealthDamageMultipliers, scaledHealthMultiplier, scaledDamageMultiplier ); } else { // Reward scaling is disabled LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Reward scaling is disabled.", creature->GetName(), creatureABInfo->selectedLevel ); } // XP Scaling if (RewardScalingXP) { if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) { creatureABInfo->XPModifier = RewardScalingXPModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Fixed Mode: XPModifier ({}) = RewardScalingXPModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->XPModifier, RewardScalingXPModifier ); } else if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) { creatureABInfo->XPModifier = avgHealthDamageMultipliers * RewardScalingXPModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Dynamic Mode: XPModifier ({}) = avgHealthDamageMultipliers ({}) * RewardScalingXPModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->XPModifier, avgHealthDamageMultipliers, RewardScalingXPModifier ); } } // Money Scaling if (RewardScalingMoney) { if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) { creatureABInfo->MoneyModifier = RewardScalingMoneyModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Fixed Mode: MoneyModifier ({}) = RewardScalingMoneyModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->MoneyModifier, RewardScalingMoneyModifier ); } else if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) { creatureABInfo->MoneyModifier = avgHealthDamageMultipliers * RewardScalingMoneyModifier; LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Dynamic Mode: MoneyModifier ({}) = avgHealthDamageMultipliers ({}) * RewardScalingMoneyModifier ({})", creature->GetName(), creatureABInfo->selectedLevel, creatureABInfo->MoneyModifier, avgHealthDamageMultipliers, RewardScalingMoneyModifier ); } } // update all stats creature->UpdateAllStats(); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | ---------- FINAL STATS ----------", creature->GetName(), creatureABInfo->selectedLevel ); LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Health ({}/{} {:.1f}%) -> ({}/{} {:.1f}%)", creature->GetName(), creatureABInfo->selectedLevel, prevHealth, prevMaxHealth, prevMaxHealth ? float(prevHealth) / float(prevMaxHealth) * 100.0f : 0.0f, creature->GetHealth(), creature->GetMaxHealth(), creature->GetMaxHealth() ? float(creature->GetHealth()) / float(creature->GetMaxHealth()) * 100.0f : 0.0f ); if (prevPower && prevMaxPower && pType == Powers::POWER_MANA) { LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Mana ({}/{} {:.1f}%) -> ({}/{} {:.1f}%)", creature->GetName(), creatureABInfo->selectedLevel, prevPower, prevMaxPower, prevMaxPower ? float(prevPower) / float(prevMaxPower) * 100.0f : 0.0f, creature->GetPower(Powers::POWER_MANA), creature->GetMaxPower(Powers::POWER_MANA), creature->GetMaxPower(Powers::POWER_MANA) ? float(creature->GetPower(Powers::POWER_MANA)) / float(creature->GetMaxPower(Powers::POWER_MANA)) * 100.0f : 0.0f ); } // debug log the new stat multipliers stored in CreatureABInfo in a compact, single-line format if (creatureABInfo->UnmodifiedLevel != creatureABInfo->selectedLevel) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}->{}) | Multipliers: H:{:.3f}->{:.3f} M:{:.3f}->{:.3f} A:{:.3f}->{:.3f} D:{:.3f}->{:.3f} CC:{:.3f} XP:{:.3f} $:{:.3f}", creature->GetName(), creatureABInfo->UnmodifiedLevel, creatureABInfo->selectedLevel, creatureABInfo->HealthMultiplier, creatureABInfo->ScaledHealthMultiplier, creatureABInfo->ManaMultiplier, creatureABInfo->ScaledManaMultiplier, creatureABInfo->ArmorMultiplier, creatureABInfo->ScaledArmorMultiplier, creatureABInfo->DamageMultiplier, creatureABInfo->ScaledDamageMultiplier, creatureABInfo->CCDurationMultiplier, creatureABInfo->XPModifier, creatureABInfo->MoneyModifier ); } else { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) | Multipliers: H:{:.3f} M:{:.3f} A:{:.3f} D:{:.3f} CC:{:.3f} XP:{:.3f} $:{:.3f}", creature->GetName(), creatureABInfo->UnmodifiedLevel, creatureABInfo->HealthMultiplier, creatureABInfo->ManaMultiplier, creatureABInfo->ArmorMultiplier, creatureABInfo->DamageMultiplier, creatureABInfo->CCDurationMultiplier, creatureABInfo->XPModifier, creatureABInfo->MoneyModifier ); } } bool AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner(Creature* summon) { // if the summon doesn't exist or isn't a summon if (!summon || !summon->IsSummon()) return false; // get the summon's info AutoBalanceCreatureInfo* summonABInfo = summon->CustomData.GetDefault("AutoBalanceCreatureInfo"); // get the saved summoner Creature* summoner = summonABInfo->summoner; // if the summoner doesn't exist if (!summoner) return false; // if this creature's ID is in the list of creatures that are not clones of their summoner (creatureIDsThatAreNotClones), return false if ( std::find ( creatureIDsThatAreNotClones.begin(), creatureIDsThatAreNotClones.end(), summon->GetEntry() ) != creatureIDsThatAreNotClones.end() ) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | creatureIDsThatAreNotClones contains this creature's ID ({}) | false", summon->GetName(), summonABInfo->selectedLevel, summon->GetEntry() ); return false; } // create a running score for this check int8 score = 0; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | Is this a clone of it's summoner {} ({})?", summon->GetName(), summonABInfo->selectedLevel, summoner->GetName(), summoner->GetLevel() ); // if the entry ID is the same, +2 if (summon->GetEntry() == summoner->GetEntry()) { score += 2; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | Entry: ({}) == ({}) | score: +2 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetEntry(), summoner->GetEntry(), score ); } // if the max health is the same, +3 if (summon->GetMaxHealth() == summoner->GetMaxHealth()) { score += 3; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | MaxHealth: ({}) == ({}) | score: +3 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetMaxHealth(), summoner->GetMaxHealth(), score ); } // if the type (humanoid, dragonkin, etc) is the same, +1 if (summon->GetCreatureType() == summoner->GetCreatureType()) { score += 1; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | CreatureType: ({}) == ({}) | score: +1 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetCreatureType(), summoner->GetCreatureType(), score ); } // if the name is the same, +2 if (summon->GetName() == summoner->GetName()) { score += 2; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | Name: ({}) == ({}) | score: +2 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetName(), summoner->GetName(), score ); } // if the summoner's name is a part of the summon's name, +1 else if (summon->GetName().find(summoner->GetName()) != std::string::npos) { score += 1; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | Name: ({}) contains ({}) | score: +1 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetName(), summoner->GetName(), score ); } // if the display ID is the same, +1 if (summon->GetDisplayId() == summoner->GetDisplayId()) { score += 1; LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | DisplayId: ({}) == ({}) | score: +1 = ({})", summon->GetName(), summonABInfo->selectedLevel, summon->GetDisplayId(), summoner->GetDisplayId(), score ); } // if the score is at least 5, consider this a clone if (score >= 5) { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | score ({}) >= 5 | true", summon->GetName(), summonABInfo->selectedLevel, score ); return true; } else { LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::_isSummonCloneOfSummoner: Creature {} ({}) | score ({}) < 5 | false", summon->GetName(), summonABInfo->selectedLevel, score ); return false; } }