Files
moonwell-core/modules/mod-autobalance/src/ABAllCreatureScript.cpp
T

1623 lines
73 KiB
C++

#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>("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>("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>("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>("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>("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>("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>("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>("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>("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;
}
}