3072 lines
117 KiB
C++
3072 lines
117 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE
|
|
*/
|
|
|
|
#include "ABUtils.h"
|
|
|
|
#include "ABConfig.h"
|
|
#include "ABCreatureInfo.h"
|
|
#include "ABMapInfo.h"
|
|
|
|
#include "Log.h"
|
|
#include "Player.h"
|
|
#include "TemporarySummon.h"
|
|
|
|
#include <chrono>
|
|
#include <sstream>
|
|
|
|
void AddCreatureToMapCreatureList(Creature* creature, bool addToCreatureList, bool forceRecalculation)
|
|
{
|
|
//
|
|
// Make sure we have a creature and that it's assigned to a map
|
|
//
|
|
|
|
if (!creature || !creature->GetMap())
|
|
return;
|
|
|
|
//
|
|
// If this isn't a dungeon, skip
|
|
//
|
|
|
|
if (!(creature->GetMap()->IsDungeon()))
|
|
return;
|
|
|
|
//
|
|
// Get AutoBalance data
|
|
//
|
|
|
|
Map* map = creature->GetMap();
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(instanceMap);
|
|
AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
|
|
//
|
|
// Handle summoned creatures
|
|
//
|
|
|
|
if (creature->IsSummon())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is a summon.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
|
|
if (creature->ToTempSummon() &&
|
|
creature->ToTempSummon()->GetSummoner() &&
|
|
creature->ToTempSummon()->GetSummoner()->ToCreature())
|
|
{
|
|
creatureABInfo->summoner = creature->ToTempSummon()->GetSummoner()->ToCreature();
|
|
creatureABInfo->summonerName = creatureABInfo->summoner->GetName();
|
|
creatureABInfo->summonerLevel = creatureABInfo->summoner->GetLevel();
|
|
|
|
Creature* summoner = creatureABInfo->summoner;
|
|
|
|
if (!summoner)
|
|
{
|
|
creatureABInfo->UnmodifiedLevel = mapABInfo->avgCreatureLevel;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is not owned by a summoner. Original level is {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
else
|
|
{
|
|
AutoBalanceCreatureInfo* summonerABInfo = summoner->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is owned by {} ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summoner->GetName(),
|
|
summonerABInfo->UnmodifiedLevel);
|
|
|
|
//
|
|
// If the creature or its summoner is a trigger
|
|
//
|
|
if (creature->IsTrigger() || summoner->IsTrigger())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | or their summoner is a trigger.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
|
|
//
|
|
// If the creature is within the expected level range, allow scaling
|
|
//
|
|
|
|
if ((creatureABInfo->UnmodifiedLevel >= (uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f)) &&
|
|
(creatureABInfo->UnmodifiedLevel <= (uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f)))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | original level is within the expected NPC level for this map ({} to {}). Level scaling is allowed.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
(uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f),
|
|
(uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f));
|
|
}
|
|
else
|
|
{
|
|
creatureABInfo->neverLevelScale = true;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | original level is outside the expected NPC level for this map ({} to {}). It will keep its original level.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
(uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f),
|
|
(uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f));
|
|
}
|
|
}
|
|
//
|
|
// If the creature is not a trigger, match the summoner's level
|
|
//
|
|
else
|
|
{
|
|
//
|
|
// Match the summoner's level
|
|
//
|
|
creatureABInfo->UnmodifiedLevel = summonerABInfo->UnmodifiedLevel;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | original level will match summoner's level ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summonerABInfo->UnmodifiedLevel
|
|
);
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Summoned by a player
|
|
//
|
|
else if(creature->ToTempSummon() &&
|
|
creature->ToTempSummon()->GetSummoner() &&
|
|
creature->ToTempSummon()->GetSummoner()->ToPlayer())
|
|
{
|
|
Player* summoner = creature->ToTempSummon()->GetSummoner()->ToPlayer();
|
|
|
|
//
|
|
// Is this creature relevant?
|
|
//
|
|
|
|
if (isCreatureRelevant(creature))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is an enemy owned by player {} ({}). Summon original level set to ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summoner->GetName(),
|
|
summoner->GetLevel(),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
//
|
|
// Summon is not relevant
|
|
//
|
|
else
|
|
{
|
|
uint8 newLevel = std::min(summoner->GetLevel(), creature->GetCreatureTemplate()->maxlevel);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is an ally owned by player {} ({}). Summon original level set to ({}) level ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summoner->GetName(),
|
|
summoner->GetLevel(),
|
|
newLevel == summoner->GetLevel() ? "player's" : "creature template's max",
|
|
newLevel);
|
|
|
|
creatureABInfo->UnmodifiedLevel = newLevel;
|
|
}
|
|
}
|
|
//
|
|
// Pets and totems
|
|
//
|
|
else if (creature->IsCreatedByPlayer() || creature->IsPet() || creature->IsHunterPet() || creature->IsTotem())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is a {}. Original level set to ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creature->IsCreatedByPlayer() ? "creature created by a player" : creature->IsPet() ? "pet" : creature->IsHunterPet() ? "hunter pet" : "totem",
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | does not have a summoner. Summon original level set to ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
|
|
//
|
|
// if this is a summon, we shouldn't track it in any list and it does not contribute to the average level
|
|
//
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | will not affect the map's stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel);
|
|
return;
|
|
}
|
|
//
|
|
// Handle "special" creatures
|
|
//
|
|
else if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger())
|
|
{
|
|
//
|
|
// If this is an intentionally-low-level creature (below 85% of the minimum LFG level), leave it where it is
|
|
// If this is an intentionally-high-level creature (above 125% of the maximum LFG level), leave it where it is
|
|
//
|
|
if ((creatureABInfo->UnmodifiedLevel < (uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f)) ||
|
|
(creatureABInfo->UnmodifiedLevel > (uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f)))
|
|
{
|
|
creatureABInfo->neverLevelScale = true;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is a {} and is outside the expected NPC level for this map ({} to {}). Keeping original level of {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creature->IsCritter() ? "critter" : creature->IsTotem() ? "totem" : "trigger",
|
|
(uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f),
|
|
(uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
//
|
|
// Otherwise, set it to the target level of the instance so it will get scaled properly
|
|
//
|
|
else
|
|
{
|
|
creatureABInfo->UnmodifiedLevel = mapABInfo->lfgTargetLevel;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) (summon) | is a {} and is within the expected NPC level for this map ({} to {}). Keeping original level of {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creature->IsCritter() ? "critter" : creature->IsTotem() ? "totem" : "trigger",
|
|
(uint8)(((float)mapABInfo->lfgMinLevel * .85f) + 0.5f),
|
|
(uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f),
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
}
|
|
//
|
|
// Creature isn't a summon, just store their unmodified level
|
|
//
|
|
else
|
|
{
|
|
creatureABInfo->UnmodifiedLevel = creatureABInfo->UnmodifiedLevel;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | Original level set to ({}).",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creatureABInfo->UnmodifiedLevel);
|
|
}
|
|
|
|
//
|
|
// If this is a creature controlled by the player, skip for stats
|
|
//
|
|
if (((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is controlled by the player and will not affect the map's stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If this is a non-relevant creature, skip for stats
|
|
//
|
|
|
|
if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is a {} and will not affect the map's stats.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creature->IsCritter() ? "critter" : creature->IsTotem() ? "totem" : "trigger");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the creature level is below 85% of the minimum LFG level, assume it's a flavor creature and shouldn't be tracked
|
|
//
|
|
|
|
if (creatureABInfo->UnmodifiedLevel < (uint8)(((float)mapABInfo->lfgMinLevel * 0.85f) + 0.5f))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is below 85% of the LFG min level of {} and will not affect the map's stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->lfgMinLevel);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the creature level is above 125% of the maximum LFG level, assume it's a flavor creature or holiday boss and shouldn't be tracked
|
|
//
|
|
|
|
if (creatureABInfo->UnmodifiedLevel > (uint8)(((float)mapABInfo->lfgMaxLevel * 1.15f) + 0.5f))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is above 115% of the LFG max level of {} and will not affect the map's stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->lfgMaxLevel);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Is this creature already in the map's creature list?
|
|
//
|
|
|
|
bool isCreatureAlreadyInCreatureList = creatureABInfo->isInCreatureList;
|
|
|
|
//
|
|
// Add the creature to the map's creature list if configured to do so
|
|
//
|
|
if (addToCreatureList && !isCreatureAlreadyInCreatureList)
|
|
{
|
|
mapABInfo->allMapCreatures.push_back(creature);
|
|
creatureABInfo->isInCreatureList = true;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is #{} in the creature list.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->allMapCreatures.size());
|
|
}
|
|
|
|
//
|
|
// Alter stats for the map if needed
|
|
//
|
|
|
|
bool isIncludedInMapStats = true;
|
|
|
|
//
|
|
// If this creature was already in the creature list, don't consider it for map stats (again)
|
|
// exception for if forceRecalculation is true (used on player enter/exit to recalculate map stats)
|
|
//
|
|
|
|
if (isCreatureAlreadyInCreatureList && !forceRecalculation)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is already included in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel);
|
|
|
|
// ensure that this creature is marked active
|
|
creatureABInfo->isActive = true;
|
|
|
|
// increment the active creature counter
|
|
mapABInfo->activeCreatureCount++;
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Only do these additional checks if we still think they need to be applied to the map stats
|
|
//
|
|
|
|
if (isIncludedInMapStats)
|
|
{
|
|
//
|
|
// If the creature is vendor, trainer, or has gossip, don't use it to update map stats
|
|
//
|
|
if ((creature->IsVendor() ||
|
|
creature->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP) ||
|
|
creature->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER) ||
|
|
creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER) ||
|
|
creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER_PROFESSION) ||
|
|
creature->HasNpcFlag(UNIT_NPC_FLAG_REPAIR) ||
|
|
creature->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) ||
|
|
creature->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) &&
|
|
(!isBossOrBossSummon(creature)))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is a a vendor, trainer, or is otherwise not attackable - do not include in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel);
|
|
|
|
isIncludedInMapStats = false;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If the creature is friendly to a player, don't use it to update map stats
|
|
//
|
|
for (std::vector<Player*>::const_iterator playerIterator = mapABInfo->allMapPlayers.begin(); playerIterator != mapABInfo->allMapPlayers.end(); ++playerIterator)
|
|
{
|
|
Player* thisPlayer = *playerIterator;
|
|
|
|
//
|
|
// If this player is a Game Master, skip
|
|
//
|
|
|
|
if (thisPlayer->IsGameMaster())
|
|
continue;
|
|
|
|
//
|
|
// If the creature is friendly and not a boss
|
|
//
|
|
|
|
if (creature->IsFriendlyTo(thisPlayer) && !isBossOrBossSummon(creature))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is friendly to {} - do not include in map stats.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
thisPlayer->GetName()
|
|
);
|
|
isIncludedInMapStats = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Perform the distance check if an override is configured for this map
|
|
//
|
|
if (hasLevelScalingDistanceCheckOverride(instanceMap->GetId()))
|
|
{
|
|
uint32 distance = levelScalingDistanceCheckOverrides[instanceMap->GetId()];
|
|
bool isPlayerWithinDistance = false;
|
|
|
|
for (std::vector<Player*>::const_iterator playerIterator = mapABInfo->allMapPlayers.begin(); playerIterator != mapABInfo->allMapPlayers.end(); ++playerIterator)
|
|
{
|
|
Player* thisPlayer = *playerIterator;
|
|
|
|
//
|
|
// If this player is a Game Master, skip
|
|
//
|
|
|
|
if (thisPlayer->IsGameMaster())
|
|
continue;
|
|
|
|
if (thisPlayer->IsWithinDist(creature, 500))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is in range ({} world units) of player {} and is considered active.", creature->GetName(), creatureABInfo->UnmodifiedLevel, distance, thisPlayer->GetName());
|
|
isPlayerWithinDistance = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is NOT in range ({} world units) of any player and is NOT considered active.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
distance
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If no players were within the distance, don't include this creature in the map stats
|
|
//
|
|
|
|
if (!isPlayerWithinDistance)
|
|
isIncludedInMapStats = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isIncludedInMapStats)
|
|
{
|
|
//
|
|
// Mark this creature as being considered in the map stats
|
|
//
|
|
|
|
creatureABInfo->isActive = true;
|
|
|
|
//
|
|
// Update the highest and lowest creature levels
|
|
//
|
|
|
|
if (creatureABInfo->UnmodifiedLevel > mapABInfo->highestCreatureLevel || mapABInfo->highestCreatureLevel == 0)
|
|
mapABInfo->highestCreatureLevel = creatureABInfo->UnmodifiedLevel;
|
|
if (creatureABInfo->UnmodifiedLevel < mapABInfo->lowestCreatureLevel || mapABInfo->lowestCreatureLevel == 0)
|
|
mapABInfo->lowestCreatureLevel = creatureABInfo->UnmodifiedLevel;
|
|
|
|
//
|
|
// Calculate the new average creature level
|
|
//
|
|
|
|
float creatureCount = mapABInfo->activeCreatureCount;
|
|
float oldAvgCreatureLevel = mapABInfo->avgCreatureLevel;
|
|
float newAvgCreatureLevel = (((float)mapABInfo->avgCreatureLevel * creatureCount) + (float)creatureABInfo->UnmodifiedLevel) / (creatureCount + 1.0f);
|
|
|
|
mapABInfo->avgCreatureLevel = newAvgCreatureLevel;
|
|
|
|
//
|
|
// Increment the active creature counter
|
|
//
|
|
|
|
mapABInfo->activeCreatureCount++;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: Creature {} ({}) | is included in map stats (active), adjusting avgCreatureLevel to ({})", creature->GetName(), creatureABInfo->UnmodifiedLevel, newAvgCreatureLevel);
|
|
|
|
//
|
|
// If the average creature level transitions from one whole number to the next, reset the map's config time so it will refresh
|
|
//
|
|
|
|
if (round(oldAvgCreatureLevel) != round(newAvgCreatureLevel))
|
|
{
|
|
mapABInfo->mapConfigTime = 1;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: {} ({}{}) | average creature level changes {}->{}. Force map update. {} ({}{}) map config set to ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
round(oldAvgCreatureLevel),
|
|
round(newAvgCreatureLevel),
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->mapConfigTime);
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddCreatureToMapCreatureList: There are ({}) creatures included (active) in map stats.", mapABInfo->activeCreatureCount);
|
|
}
|
|
}
|
|
|
|
void RemoveCreatureFromMapData(Creature* creature)
|
|
{
|
|
//
|
|
// Get map data
|
|
//
|
|
|
|
AutoBalanceMapInfo *mapABInfo = GetMapInfo(creature->GetMap());
|
|
|
|
//
|
|
// If the creature is in the all creature list, remove it
|
|
//
|
|
|
|
if (mapABInfo->allMapCreatures.size() > 0)
|
|
{
|
|
for (std::vector<Creature*>::iterator creatureIteration = mapABInfo->allMapCreatures.begin(); creatureIteration != mapABInfo->allMapCreatures.end(); ++creatureIteration)
|
|
{
|
|
if (*creatureIteration == creature)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::RemoveCreatureFromMapData: Creature {} ({}) | is in the creature list and will be removed. There are {} creatures left.", creature->GetName(), creature->GetLevel(), mapABInfo->allMapCreatures.size() - 1);
|
|
mapABInfo->allMapCreatures.erase(creatureIteration);
|
|
|
|
//
|
|
// Mark this creature as removed
|
|
//
|
|
|
|
AutoBalanceCreatureInfo *creatureABInfo = creature->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
creatureABInfo->isInCreatureList = false;
|
|
|
|
//
|
|
// Decrement the active creature counter if they were considered active
|
|
//
|
|
|
|
if (creatureABInfo->isActive)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::RemoveCreatureFromMapData: Creature {} ({}) | is no longer active. There are {} active creatures left.",
|
|
creature->GetName(),
|
|
creature->GetLevel(),
|
|
mapABInfo->activeCreatureCount - 1);
|
|
|
|
if (mapABInfo->activeCreatureCount > 0)
|
|
mapABInfo->activeCreatureCount--;
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::RemoveCreatureFromMapData: Map {} ({}{}) | activeCreatureCount is already 0. This should not happen.",
|
|
creature->GetMap()->GetMapName(),
|
|
creature->GetMap()->GetId(),
|
|
creature->GetMap()->GetInstanceId() ? "-" + std::to_string(creature->GetMap()->GetInstanceId()) : "");
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t GetCurrentConfigTime()
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
uint32 getBaseExpansionValueForLevel(const uint32 baseValues[3], uint8 targetLevel)
|
|
{
|
|
// convert baseValues from an array of uint32 to an array of float
|
|
float floatBaseValues[3];
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
floatBaseValues[i] = (float)baseValues[i];
|
|
|
|
// return the result
|
|
return getBaseExpansionValueForLevel(floatBaseValues, targetLevel);
|
|
}
|
|
|
|
float getBaseExpansionValueForLevel(const float baseValues[3], uint8 targetLevel)
|
|
{
|
|
// the database holds multiple base values depending on the expansion
|
|
// this function returns the correct base value for the given level and
|
|
// smooths the transition between expansions
|
|
|
|
float vanillaValue = baseValues[0];
|
|
float bcValue = baseValues[1];
|
|
float wotlkValue = baseValues[2];
|
|
|
|
float returnValue;
|
|
|
|
// vanilla
|
|
if (targetLevel <= 60)
|
|
{
|
|
returnValue = vanillaValue;
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::getBaseExpansionValueForLevel: Returning Vanilla = {}", returnValue);
|
|
}
|
|
// transition from vanilla to BC
|
|
else if (targetLevel < 63)
|
|
{
|
|
float vanillaMultiplier = (63 - targetLevel) / 3.0;
|
|
float bcMultiplier = 1.0f - vanillaMultiplier;
|
|
|
|
returnValue = (vanillaValue * vanillaMultiplier) + (bcValue * bcMultiplier);
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::getBaseExpansionValueForLevel: Returning Vanilla/BC = {}", returnValue);
|
|
}
|
|
// BC
|
|
else if (targetLevel <= 70)
|
|
{
|
|
returnValue = bcValue;
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::getBaseExpansionValueForLevel: Returning BC = {}", returnValue);
|
|
}
|
|
// transition from BC to WotLK
|
|
else if (targetLevel < 73)
|
|
{
|
|
float bcMultiplier = (73 - targetLevel) / 3.0f;
|
|
float wotlkMultiplier = 1.0f - bcMultiplier;
|
|
|
|
returnValue = (bcValue * bcMultiplier) + (wotlkValue * wotlkMultiplier);
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::getBaseExpansionValueForLevel: Returning BC/WotLK = {}", returnValue);
|
|
}
|
|
// WotLK
|
|
else
|
|
{
|
|
returnValue = wotlkValue;
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::getBaseExpansionValueForLevel: Returning WotLK = {}", returnValue);
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
float getDefaultMultiplier(Map* map, AutoBalanceInflectionPointSettings inflectionPointSettings)
|
|
{
|
|
// You can visually see the effects of this function by using this spreadsheet:
|
|
// https://docs.google.com/spreadsheets/d/100cmKIJIjCZ-ncWd0K9ykO8KUgwFTcwg4h2nfE_UeCc/copy
|
|
|
|
//
|
|
// Get the max player count for the map
|
|
//
|
|
uint32 maxNumberOfPlayers = map->ToInstanceMap()->GetMaxPlayers();
|
|
|
|
//
|
|
// Get the adjustedPlayerCount for this instance
|
|
//
|
|
AutoBalanceMapInfo *mapABInfo = GetMapInfo(map);
|
|
float adjustedPlayerCount = mapABInfo->adjustedPlayerCount;
|
|
|
|
//
|
|
// #maththings
|
|
//
|
|
float diff = ((float)maxNumberOfPlayers/5)*1.5f;
|
|
|
|
//
|
|
// For math reasons that I do not understand, curveCeiling needs to be adjusted to bring the actual multiplier
|
|
// closer to the curveCeiling setting. Create an adjustment based on how much the ceiling should be changed at
|
|
// the max players multiplier.
|
|
//
|
|
float curveCeilingAdjustment =
|
|
inflectionPointSettings.curveCeiling /
|
|
(((tanh(((float)maxNumberOfPlayers - inflectionPointSettings.value) / diff) + 1.0f) / 2.0f) *
|
|
(inflectionPointSettings.curveCeiling - inflectionPointSettings.curveFloor) + inflectionPointSettings.curveFloor);
|
|
|
|
//
|
|
// Adjust the multiplier based on the configured floor and ceiling values, plus the ceiling adjustment we just calculated
|
|
//
|
|
float defaultMultiplier =
|
|
((tanh((adjustedPlayerCount - inflectionPointSettings.value) / diff) + 1.0f) / 2.0f) *
|
|
(inflectionPointSettings.curveCeiling * curveCeilingAdjustment - inflectionPointSettings.curveFloor) +
|
|
inflectionPointSettings.curveFloor;
|
|
|
|
return defaultMultiplier;
|
|
}
|
|
|
|
int GetForcedNumPlayers(int creatureId)
|
|
{
|
|
if (forcedCreatureIds.find(creatureId) == forcedCreatureIds.end()) // Don't want the forcedCreatureIds map to blowup to a massive empty array
|
|
return -1;
|
|
|
|
return forcedCreatureIds[creatureId];
|
|
}
|
|
|
|
World_Multipliers getWorldMultiplier(Map* map, BaseValueType baseValueType)
|
|
{
|
|
World_Multipliers worldMultipliers;
|
|
|
|
//
|
|
// null check
|
|
//
|
|
|
|
if (!map)
|
|
return worldMultipliers;
|
|
|
|
//
|
|
// If this isn't a dungeon, return defaults
|
|
//
|
|
|
|
if (!(map->IsDungeon()))
|
|
return worldMultipliers;
|
|
|
|
//
|
|
// Grab map data
|
|
//
|
|
AutoBalanceMapInfo *mapABInfo = GetMapInfo(map);
|
|
|
|
//
|
|
// If the map isn't enabled, return defaults
|
|
//
|
|
|
|
if (!mapABInfo->enabled)
|
|
return worldMultipliers;
|
|
|
|
//
|
|
// If there are no players on the map, return defaults
|
|
//
|
|
|
|
if (mapABInfo->allMapPlayers.size() == 0)
|
|
return worldMultipliers;
|
|
|
|
//
|
|
// If creatures haven't been counted yet, return defaults
|
|
//
|
|
|
|
if (mapABInfo->avgCreatureLevel == 0)
|
|
return worldMultipliers;
|
|
|
|
//
|
|
// Create some data variables
|
|
//
|
|
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
uint8 avgCreatureLevelRounded = (uint8)(mapABInfo->avgCreatureLevel + 0.5f);
|
|
|
|
//
|
|
// Get the inflection point settings for this map
|
|
//
|
|
|
|
AutoBalanceInflectionPointSettings inflectionPointSettings = getInflectionPointSettings(instanceMap);
|
|
|
|
//
|
|
// Generate the default multiplier before level scaling
|
|
// This value is only based on the adjusted number of players in the instance
|
|
//
|
|
|
|
float worldMultiplier = 1.0f;
|
|
float defaultMultiplier = getDefaultMultiplier(map, inflectionPointSettings);
|
|
|
|
LOG_DEBUG("module.AutoBalance",
|
|
"AutoBalance::getWorldMultiplier: Map {} ({}) {} | defaultMultiplier ({}) = getDefaultMultiplier(map, inflectionPointSettings)",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
defaultMultiplier
|
|
);
|
|
|
|
//
|
|
// Multiply by the appropriate stat modifiers
|
|
//
|
|
AutoBalanceStatModifiers statModifiers = getStatModifiers(map);
|
|
|
|
if (baseValueType == BaseValueType::AUTOBALANCE_HEALTH) // health
|
|
worldMultiplier = defaultMultiplier * statModifiers.global * statModifiers.health;
|
|
else // damage
|
|
worldMultiplier = defaultMultiplier * statModifiers.global * statModifiers.damage;
|
|
|
|
LOG_DEBUG("module.AutoBalance",
|
|
"AutoBalance::getWorldMultiplier: Map {} ({}) {} | worldMultiplier ({}) = defaultMultiplier ({}) * statModifiers.global ({}) * statModifiers.{} ({})",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
worldMultiplier,
|
|
defaultMultiplier,
|
|
statModifiers.global,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? statModifiers.health : statModifiers.damage
|
|
);
|
|
|
|
//
|
|
// Store the unscaled multiplier
|
|
//
|
|
worldMultipliers.unscaled = worldMultiplier;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) {} | multiplier before level scaling = ({}).",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
worldMultiplier
|
|
);
|
|
|
|
//
|
|
// Only scale based on level 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)
|
|
)
|
|
)
|
|
{
|
|
mapABInfo->worldMultiplierTargetLevel = mapABInfo->highestPlayerLevel;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) {} | level will be scaled to {}.",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
mapABInfo->worldMultiplierTargetLevel
|
|
);
|
|
|
|
//
|
|
// Use creature base stats to determine how to level scale the multiplier (the map is a warrior!)
|
|
//
|
|
|
|
CreatureBaseStats const* origMapBaseStats = sObjectMgr->GetCreatureBaseStats(avgCreatureLevelRounded, Classes::CLASS_WARRIOR);
|
|
CreatureBaseStats const* adjustedMapBaseStats = sObjectMgr->GetCreatureBaseStats(mapABInfo->worldMultiplierTargetLevel, Classes::CLASS_WARRIOR);
|
|
|
|
//
|
|
// Original Base Value
|
|
//
|
|
|
|
float originalBaseValue;
|
|
|
|
if (baseValueType == BaseValueType::AUTOBALANCE_HEALTH) // health
|
|
{
|
|
originalBaseValue = getBaseExpansionValueForLevel(
|
|
origMapBaseStats->BaseHealth,
|
|
avgCreatureLevelRounded
|
|
);
|
|
}
|
|
else // damage
|
|
{
|
|
originalBaseValue = getBaseExpansionValueForLevel(
|
|
origMapBaseStats->BaseDamage,
|
|
avgCreatureLevelRounded
|
|
);
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) {} | base is {}.",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
originalBaseValue
|
|
);
|
|
|
|
//
|
|
// New Base Value
|
|
//
|
|
|
|
float newBaseValue;
|
|
|
|
if (baseValueType == BaseValueType::AUTOBALANCE_HEALTH) // health
|
|
{
|
|
newBaseValue = getBaseExpansionValueForLevel(
|
|
adjustedMapBaseStats->BaseHealth,
|
|
mapABInfo->worldMultiplierTargetLevel
|
|
);
|
|
}
|
|
else // damage
|
|
{
|
|
newBaseValue = getBaseExpansionValueForLevel(
|
|
adjustedMapBaseStats->BaseDamage,
|
|
mapABInfo->worldMultiplierTargetLevel
|
|
);
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}->{}) {} | base is {}.",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
newBaseValue
|
|
);
|
|
|
|
//
|
|
// Update the world multiplier accordingly
|
|
//
|
|
|
|
worldMultiplier *= newBaseValue / originalBaseValue;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}->{}) {} | worldMultiplier ({}) = worldMultiplier ({}) * newBaseValue ({}) / originalBaseValue ({})",
|
|
map->GetMapName(),
|
|
mapABInfo->avgCreatureLevel,
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
worldMultiplier,
|
|
worldMultiplier,
|
|
newBaseValue,
|
|
originalBaseValue
|
|
);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}->{}) {} | multiplier after level scaling = ({}).",
|
|
map->GetMapName(),
|
|
avgCreatureLevelRounded,
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
worldMultiplier
|
|
);
|
|
}
|
|
else
|
|
{
|
|
mapABInfo->worldMultiplierTargetLevel = avgCreatureLevelRounded;
|
|
|
|
//
|
|
// Level scaling is disabled
|
|
//
|
|
|
|
if (!LevelScaling)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) | not level scaled due to level scaling being disabled. World multiplier target level set to avgCreatureLevel ({}).",
|
|
map->GetMapName(),
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
mapABInfo->worldMultiplierTargetLevel
|
|
);
|
|
}
|
|
//
|
|
// Inside the level skip range
|
|
//
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) | not level scaled due to being inside the level skip range. World multiplier target level set to avgCreatureLevel ({}).",
|
|
map->GetMapName(),
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
mapABInfo->worldMultiplierTargetLevel
|
|
);
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::getWorldMultiplier: Map {} ({}) {} | multiplier after level scaling = ({}).",
|
|
map->GetMapName(),
|
|
mapABInfo->worldMultiplierTargetLevel,
|
|
baseValueType == BaseValueType::AUTOBALANCE_HEALTH ? "health" : "damage",
|
|
worldMultiplier
|
|
);
|
|
}
|
|
|
|
//
|
|
// Store the (potentially) level-scaled multiplier
|
|
//
|
|
|
|
worldMultipliers.scaled = worldMultiplier;
|
|
|
|
return worldMultipliers;
|
|
}
|
|
|
|
AutoBalanceInflectionPointSettings getInflectionPointSettings (InstanceMap* instanceMap, bool isBoss)
|
|
{
|
|
uint32 maxNumberOfPlayers = instanceMap->GetMaxPlayers();
|
|
uint32 mapId = instanceMap->GetEntry()->MapID;
|
|
|
|
float inflectionValue = (float)maxNumberOfPlayers;
|
|
float curveFloor;
|
|
float curveCeiling;
|
|
|
|
//
|
|
// Base Inflection Point
|
|
//
|
|
|
|
if (instanceMap->IsHeroic())
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
{
|
|
inflectionValue *= InflectionPointHeroic;
|
|
curveFloor = InflectionPointHeroicCurveFloor;
|
|
curveCeiling = InflectionPointHeroicCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 10)
|
|
{
|
|
inflectionValue *= InflectionPointRaid10MHeroic;
|
|
curveFloor = InflectionPointRaid10MHeroicCurveFloor;
|
|
curveCeiling = InflectionPointRaid10MHeroicCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 25)
|
|
{
|
|
inflectionValue *= InflectionPointRaid25MHeroic;
|
|
curveFloor = InflectionPointRaid25MHeroicCurveFloor;
|
|
curveCeiling = InflectionPointRaid25MHeroicCurveCeiling;
|
|
}
|
|
else
|
|
{
|
|
inflectionValue *= InflectionPointRaidHeroic;
|
|
curveFloor = InflectionPointRaidHeroicCurveFloor;
|
|
curveCeiling = InflectionPointRaidHeroicCurveCeiling;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
{
|
|
inflectionValue *= InflectionPoint;
|
|
curveFloor = InflectionPointCurveFloor;
|
|
curveCeiling = InflectionPointCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 10)
|
|
{
|
|
inflectionValue *= InflectionPointRaid10M;
|
|
curveFloor = InflectionPointRaid10MCurveFloor;
|
|
curveCeiling = InflectionPointRaid10MCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 15)
|
|
{
|
|
inflectionValue *= InflectionPointRaid15M;
|
|
curveFloor = InflectionPointRaid15MCurveFloor;
|
|
curveCeiling = InflectionPointRaid15MCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 20)
|
|
{
|
|
inflectionValue *= InflectionPointRaid20M;
|
|
curveFloor = InflectionPointRaid20MCurveFloor;
|
|
curveCeiling = InflectionPointRaid20MCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 25)
|
|
{
|
|
inflectionValue *= InflectionPointRaid25M;
|
|
curveFloor = InflectionPointRaid25MCurveFloor;
|
|
curveCeiling = InflectionPointRaid25MCurveCeiling;
|
|
}
|
|
else if (maxNumberOfPlayers <= 40)
|
|
{
|
|
inflectionValue *= InflectionPointRaid40M;
|
|
curveFloor = InflectionPointRaid40MCurveFloor;
|
|
curveCeiling = InflectionPointRaid40MCurveCeiling;
|
|
}
|
|
else
|
|
{
|
|
inflectionValue *= InflectionPointRaid;
|
|
curveFloor = InflectionPointRaidCurveFloor;
|
|
curveCeiling = InflectionPointRaidCurveCeiling;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Per map ID overrides alter the above settings, if set
|
|
//
|
|
|
|
if (hasDungeonOverride(mapId))
|
|
{
|
|
AutoBalanceInflectionPointSettings* myInflectionPointOverrides = &dungeonOverrides[mapId];
|
|
|
|
//
|
|
// Alter the inflectionValue according to the override, if set
|
|
//
|
|
|
|
if (myInflectionPointOverrides->value != -1)
|
|
{
|
|
inflectionValue = (float)maxNumberOfPlayers; // Starting over
|
|
inflectionValue *= myInflectionPointOverrides->value;
|
|
}
|
|
|
|
if (myInflectionPointOverrides->curveFloor != -1)
|
|
curveFloor = myInflectionPointOverrides->curveFloor;
|
|
if (myInflectionPointOverrides->curveCeiling != -1)
|
|
curveCeiling = myInflectionPointOverrides->curveCeiling;
|
|
}
|
|
|
|
//
|
|
// Boss Inflection Point
|
|
//
|
|
|
|
if (isBoss) {
|
|
|
|
float bossInflectionPointMultiplier;
|
|
|
|
if (instanceMap->IsHeroic())
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
bossInflectionPointMultiplier = InflectionPointHeroicBoss;
|
|
else if (maxNumberOfPlayers <= 10)
|
|
bossInflectionPointMultiplier = InflectionPointRaid10MHeroicBoss;
|
|
else if (maxNumberOfPlayers <= 25)
|
|
bossInflectionPointMultiplier = InflectionPointRaid25MHeroicBoss;
|
|
else
|
|
bossInflectionPointMultiplier = InflectionPointRaidHeroicBoss;
|
|
}
|
|
else
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
bossInflectionPointMultiplier = InflectionPointBoss;
|
|
else if (maxNumberOfPlayers <= 10)
|
|
bossInflectionPointMultiplier = InflectionPointRaid10MBoss;
|
|
else if (maxNumberOfPlayers <= 15)
|
|
bossInflectionPointMultiplier = InflectionPointRaid15MBoss;
|
|
else if (maxNumberOfPlayers <= 20)
|
|
bossInflectionPointMultiplier = InflectionPointRaid20MBoss;
|
|
else if (maxNumberOfPlayers <= 25)
|
|
bossInflectionPointMultiplier = InflectionPointRaid25MBoss;
|
|
else if (maxNumberOfPlayers <= 40)
|
|
bossInflectionPointMultiplier = InflectionPointRaid40MBoss;
|
|
else
|
|
bossInflectionPointMultiplier = InflectionPointRaidBoss;
|
|
}
|
|
|
|
//
|
|
// Per map ID overrides alter the above settings, if set
|
|
//
|
|
|
|
if (hasBossOverride(mapId))
|
|
{
|
|
AutoBalanceInflectionPointSettings* myBossOverrides = &bossOverrides[mapId];
|
|
|
|
//
|
|
// If set, alter the inflectionValue according to the override
|
|
//
|
|
if (myBossOverrides->value != -1)
|
|
inflectionValue *= myBossOverrides->value;
|
|
//
|
|
// Otherwise, calculate using the value determined by instance type
|
|
//
|
|
else
|
|
inflectionValue *= bossInflectionPointMultiplier;
|
|
}
|
|
//
|
|
// No override, use the value determined by the instance type
|
|
//
|
|
else
|
|
inflectionValue *= bossInflectionPointMultiplier;
|
|
}
|
|
|
|
return AutoBalanceInflectionPointSettings(inflectionValue, curveFloor, curveCeiling);
|
|
}
|
|
|
|
void getStatModifiersDebug(Map *map, Creature *creature, std::string message)
|
|
{
|
|
// if we have a creature, include that in the output
|
|
if (creature)
|
|
{
|
|
// get the creature's info
|
|
AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
|
|
LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance::getStatModifiers: Map {} ({}{}) | Creature {} ({}{}) | {}",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creatureABInfo->selectedLevel ? "->" + std::to_string(creatureABInfo->selectedLevel) : "",
|
|
message
|
|
);
|
|
}
|
|
// if no creature was provided, remove that from the output
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance::getStatModifiers: Map {} ({}{}) | {}",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
message
|
|
);
|
|
}
|
|
}
|
|
|
|
AutoBalanceStatModifiers getStatModifiers (Map* map, Creature* creature)
|
|
{
|
|
//
|
|
// get the instance's InstanceMap
|
|
//
|
|
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
|
|
//
|
|
// map variables
|
|
//
|
|
|
|
uint32 maxNumberOfPlayers = instanceMap->GetMaxPlayers();
|
|
uint32 mapId = map->GetId();
|
|
|
|
//
|
|
// get the creature's info if a creature was specified
|
|
//
|
|
AutoBalanceCreatureInfo* creatureABInfo = nullptr;
|
|
|
|
if (creature)
|
|
creatureABInfo = creature->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
|
|
//
|
|
// this will be the return value
|
|
//
|
|
|
|
AutoBalanceStatModifiers statModifiers;
|
|
|
|
//
|
|
// Apply the per-instance-type modifiers first
|
|
// AutoBalance.StatModifier*(.Boss).<stat>
|
|
//
|
|
|
|
if (instanceMap->IsHeroic()) // heroic
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierHeroic_Boss_Global;
|
|
statModifiers.health = StatModifierHeroic_Boss_Health;
|
|
statModifiers.mana = StatModifierHeroic_Boss_Mana;
|
|
statModifiers.armor = StatModifierHeroic_Boss_Armor;
|
|
statModifiers.damage = StatModifierHeroic_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierHeroic_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "1 to 5 Player Heroic Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierHeroic_Global;
|
|
statModifiers.health = StatModifierHeroic_Health;
|
|
statModifiers.mana = StatModifierHeroic_Mana;
|
|
statModifiers.armor = StatModifierHeroic_Armor;
|
|
statModifiers.damage = StatModifierHeroic_Damage;
|
|
statModifiers.ccduration = StatModifierHeroic_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "1 to 5 Player Heroic");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 10)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid10MHeroic_Boss_Global;
|
|
statModifiers.health = StatModifierRaid10MHeroic_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid10MHeroic_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid10MHeroic_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid10MHeroic_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid10MHeroic_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "10 Player Heroic Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid10MHeroic_Global;
|
|
statModifiers.health = StatModifierRaid10MHeroic_Health;
|
|
statModifiers.mana = StatModifierRaid10MHeroic_Mana;
|
|
statModifiers.armor = StatModifierRaid10MHeroic_Armor;
|
|
statModifiers.damage = StatModifierRaid10MHeroic_Damage;
|
|
statModifiers.ccduration = StatModifierRaid10MHeroic_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "10 Player Heroic");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 25)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid25MHeroic_Boss_Global;
|
|
statModifiers.health = StatModifierRaid25MHeroic_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid25MHeroic_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid25MHeroic_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid25MHeroic_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid25MHeroic_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "25 Player Heroic Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid25MHeroic_Global;
|
|
statModifiers.health = StatModifierRaid25MHeroic_Health;
|
|
statModifiers.mana = StatModifierRaid25MHeroic_Mana;
|
|
statModifiers.armor = StatModifierRaid25MHeroic_Armor;
|
|
statModifiers.damage = StatModifierRaid25MHeroic_Damage;
|
|
statModifiers.ccduration = StatModifierRaid25MHeroic_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "25 Player Heroic");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaidHeroic_Boss_Global;
|
|
statModifiers.health = StatModifierRaidHeroic_Boss_Health;
|
|
statModifiers.mana = StatModifierRaidHeroic_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaidHeroic_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaidHeroic_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaidHeroic_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "?? Player Heroic Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaidHeroic_Global;
|
|
statModifiers.health = StatModifierRaidHeroic_Health;
|
|
statModifiers.mana = StatModifierRaidHeroic_Mana;
|
|
statModifiers.armor = StatModifierRaidHeroic_Armor;
|
|
statModifiers.damage = StatModifierRaidHeroic_Damage;
|
|
statModifiers.ccduration = StatModifierRaidHeroic_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "?? Player Heroic");
|
|
}
|
|
}
|
|
}
|
|
else // non-heroic
|
|
{
|
|
if (maxNumberOfPlayers <= 5)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifier_Boss_Global;
|
|
statModifiers.health = StatModifier_Boss_Health;
|
|
statModifiers.mana = StatModifier_Boss_Mana;
|
|
statModifiers.armor = StatModifier_Boss_Armor;
|
|
statModifiers.damage = StatModifier_Boss_Damage;
|
|
statModifiers.ccduration = StatModifier_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "1 to 5 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifier_Global;
|
|
statModifiers.health = StatModifier_Health;
|
|
statModifiers.mana = StatModifier_Mana;
|
|
statModifiers.armor = StatModifier_Armor;
|
|
statModifiers.damage = StatModifier_Damage;
|
|
statModifiers.ccduration = StatModifier_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "1 to 5 Player Normal");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 10)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid10M_Boss_Global;
|
|
statModifiers.health = StatModifierRaid10M_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid10M_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid10M_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid10M_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid10M_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "10 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid10M_Global;
|
|
statModifiers.health = StatModifierRaid10M_Health;
|
|
statModifiers.mana = StatModifierRaid10M_Mana;
|
|
statModifiers.armor = StatModifierRaid10M_Armor;
|
|
statModifiers.damage = StatModifierRaid10M_Damage;
|
|
statModifiers.ccduration = StatModifierRaid10M_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "10 Player Normal");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 15)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid15M_Boss_Global;
|
|
statModifiers.health = StatModifierRaid15M_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid15M_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid15M_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid15M_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid15M_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "15 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid15M_Global;
|
|
statModifiers.health = StatModifierRaid15M_Health;
|
|
statModifiers.mana = StatModifierRaid15M_Mana;
|
|
statModifiers.armor = StatModifierRaid15M_Armor;
|
|
statModifiers.damage = StatModifierRaid15M_Damage;
|
|
statModifiers.ccduration = StatModifierRaid15M_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "15 Player Normal");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 20)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid20M_Boss_Global;
|
|
statModifiers.health = StatModifierRaid20M_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid20M_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid20M_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid20M_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid20M_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "20 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid20M_Global;
|
|
statModifiers.health = StatModifierRaid20M_Health;
|
|
statModifiers.mana = StatModifierRaid20M_Mana;
|
|
statModifiers.armor = StatModifierRaid20M_Armor;
|
|
statModifiers.damage = StatModifierRaid20M_Damage;
|
|
statModifiers.ccduration = StatModifierRaid20M_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "20 Player Normal");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 25)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid25M_Boss_Global;
|
|
statModifiers.health = StatModifierRaid25M_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid25M_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid25M_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid25M_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid25M_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "25 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid25M_Global;
|
|
statModifiers.health = StatModifierRaid25M_Health;
|
|
statModifiers.mana = StatModifierRaid25M_Mana;
|
|
statModifiers.armor = StatModifierRaid25M_Armor;
|
|
statModifiers.damage = StatModifierRaid25M_Damage;
|
|
statModifiers.ccduration = StatModifierRaid25M_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "25 Player Normal");
|
|
}
|
|
}
|
|
else if (maxNumberOfPlayers <= 40)
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid40M_Boss_Global;
|
|
statModifiers.health = StatModifierRaid40M_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid40M_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid40M_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid40M_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid40M_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "40 Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid40M_Global;
|
|
statModifiers.health = StatModifierRaid40M_Health;
|
|
statModifiers.mana = StatModifierRaid40M_Mana;
|
|
statModifiers.armor = StatModifierRaid40M_Armor;
|
|
statModifiers.damage = StatModifierRaid40M_Damage;
|
|
statModifiers.ccduration = StatModifierRaid40M_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "40 Player Normal");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (creature && isBossOrBossSummon(creature))
|
|
{
|
|
statModifiers.global = StatModifierRaid_Boss_Global;
|
|
statModifiers.health = StatModifierRaid_Boss_Health;
|
|
statModifiers.mana = StatModifierRaid_Boss_Mana;
|
|
statModifiers.armor = StatModifierRaid_Boss_Armor;
|
|
statModifiers.damage = StatModifierRaid_Boss_Damage;
|
|
statModifiers.ccduration = StatModifierRaid_Boss_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "?? Player Normal Boss");
|
|
}
|
|
else
|
|
{
|
|
statModifiers.global = StatModifierRaid_Global;
|
|
statModifiers.health = StatModifierRaid_Health;
|
|
statModifiers.mana = StatModifierRaid_Mana;
|
|
statModifiers.armor = StatModifierRaid_Armor;
|
|
statModifiers.damage = StatModifierRaid_Damage;
|
|
statModifiers.ccduration = StatModifierRaid_CCDuration;
|
|
|
|
getStatModifiersDebug(map, creature, "?? Player Normal");
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Per-Map Overrides
|
|
// AutoBalance.StatModifier.Boss.PerInstance
|
|
//
|
|
|
|
if (creature && isBossOrBossSummon(creature) && hasStatModifierBossOverride(mapId))
|
|
{
|
|
AutoBalanceStatModifiers* myStatModifierBossOverrides = &statModifierBossOverrides[mapId];
|
|
|
|
if (myStatModifierBossOverrides->global != -1)
|
|
statModifiers.global = myStatModifierBossOverrides->global;
|
|
|
|
if (myStatModifierBossOverrides->health != -1)
|
|
statModifiers.health = myStatModifierBossOverrides->health;
|
|
|
|
if (myStatModifierBossOverrides->mana != -1)
|
|
statModifiers.mana = myStatModifierBossOverrides->mana;
|
|
|
|
if (myStatModifierBossOverrides->armor != -1)
|
|
statModifiers.armor = myStatModifierBossOverrides->armor;
|
|
|
|
if (myStatModifierBossOverrides->damage != -1)
|
|
statModifiers.damage = myStatModifierBossOverrides->damage;
|
|
|
|
if (myStatModifierBossOverrides->ccduration != -1)
|
|
statModifiers.ccduration = myStatModifierBossOverrides->ccduration;
|
|
|
|
getStatModifiersDebug(map, creature, "Boss Per-Instance Override");
|
|
}
|
|
//
|
|
// AutoBalance.StatModifier.PerInstance
|
|
//
|
|
else if (hasStatModifierOverride(mapId))
|
|
{
|
|
AutoBalanceStatModifiers* myStatModifierOverrides = &statModifierOverrides[mapId];
|
|
|
|
if (myStatModifierOverrides->global != -1)
|
|
statModifiers.global = myStatModifierOverrides->global;
|
|
|
|
if (myStatModifierOverrides->health != -1)
|
|
statModifiers.health = myStatModifierOverrides->health;
|
|
|
|
if (myStatModifierOverrides->mana != -1)
|
|
statModifiers.mana = myStatModifierOverrides->mana;
|
|
|
|
if (myStatModifierOverrides->armor != -1)
|
|
statModifiers.armor = myStatModifierOverrides->armor;
|
|
|
|
if (myStatModifierOverrides->damage != -1)
|
|
statModifiers.damage = myStatModifierOverrides->damage;
|
|
|
|
if (myStatModifierOverrides->ccduration != -1)
|
|
statModifiers.ccduration = myStatModifierOverrides->ccduration;
|
|
|
|
getStatModifiersDebug(map, creature, "Per-Instance Override");
|
|
}
|
|
|
|
//
|
|
// Per-creature modifiers applied last
|
|
// AutoBalance.StatModifier.PerCreature
|
|
//
|
|
if (creature && hasStatModifierCreatureOverride(creature->GetEntry()))
|
|
{
|
|
AutoBalanceStatModifiers* myCreatureOverrides = &statModifierCreatureOverrides[creature->GetEntry()];
|
|
|
|
if (myCreatureOverrides->global != -1)
|
|
statModifiers.global = myCreatureOverrides->global;
|
|
|
|
if (myCreatureOverrides->health != -1)
|
|
statModifiers.health = myCreatureOverrides->health;
|
|
|
|
if (myCreatureOverrides->mana != -1)
|
|
statModifiers.mana = myCreatureOverrides->mana;
|
|
|
|
if (myCreatureOverrides->armor != -1)
|
|
statModifiers.armor = myCreatureOverrides->armor;
|
|
|
|
if (myCreatureOverrides->damage != -1)
|
|
statModifiers.damage = myCreatureOverrides->damage;
|
|
|
|
if (myCreatureOverrides->ccduration != -1)
|
|
statModifiers.ccduration = myCreatureOverrides->ccduration;
|
|
|
|
getStatModifiersDebug(map, creature, "Per-Creature Override");
|
|
}
|
|
|
|
if (creature)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance::getStatModifiers: Map {} ({}{}) | Creature {} ({}{}) | Stat Modifiers = global: {} | health: {} | mana: {} | armor: {} | damage: {} | ccduration: {}",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
creatureABInfo->selectedLevel ? "->" + std::to_string(creatureABInfo->selectedLevel) : "",
|
|
statModifiers.global,
|
|
statModifiers.health,
|
|
statModifiers.mana,
|
|
statModifiers.armor,
|
|
statModifiers.damage,
|
|
statModifiers.ccduration == -1 ? 1.0f : statModifiers.ccduration
|
|
);
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance_StatGeneration", "AutoBalance::getStatModifiers: Map {} ({}{}) | Stat Modifiers = global: {} | health: {} | mana: {} | armor: {} | damage: {} | ccduration: {}",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
statModifiers.global,
|
|
statModifiers.health,
|
|
statModifiers.mana,
|
|
statModifiers.armor,
|
|
statModifiers.damage,
|
|
statModifiers.ccduration == -1 ? 1.0f : statModifiers.ccduration
|
|
);
|
|
}
|
|
|
|
return statModifiers;
|
|
}
|
|
|
|
bool hasBossOverride(uint32 dungeonId)
|
|
{
|
|
return (bossOverrides.find(dungeonId) != bossOverrides.end());
|
|
}
|
|
|
|
bool hasDungeonOverride(uint32 dungeonId)
|
|
{
|
|
return (dungeonOverrides.find(dungeonId) != dungeonOverrides.end());
|
|
}
|
|
|
|
bool hasDynamicLevelOverride(uint32 dungeonId)
|
|
{
|
|
return (levelScalingDynamicLevelOverrides.find(dungeonId) != levelScalingDynamicLevelOverrides.end());
|
|
}
|
|
|
|
bool hasLevelScalingDistanceCheckOverride(uint32 dungeonId)
|
|
{
|
|
return (levelScalingDistanceCheckOverrides.find(dungeonId) != levelScalingDistanceCheckOverrides.end());
|
|
}
|
|
|
|
bool hasStatModifierBossOverride(uint32 dungeonId)
|
|
{
|
|
return (statModifierBossOverrides.find(dungeonId) != statModifierBossOverrides.end());
|
|
}
|
|
|
|
bool hasStatModifierCreatureOverride(uint32 creatureId)
|
|
{
|
|
return (statModifierCreatureOverrides.find(creatureId) != statModifierCreatureOverrides.end());
|
|
}
|
|
|
|
bool hasStatModifierOverride(uint32 dungeonId)
|
|
{
|
|
return (statModifierOverrides.find(dungeonId) != statModifierOverrides.end());
|
|
}
|
|
|
|
bool isDungeonInDisabledDungeonIds(uint32 dungeonId)
|
|
{
|
|
return (std::find(disabledDungeonIds.begin(), disabledDungeonIds.end(), dungeonId) != disabledDungeonIds.end());
|
|
}
|
|
|
|
bool isBossOrBossSummon(Creature* creature, bool log)
|
|
{
|
|
// no creature? not a boss
|
|
if (!creature)
|
|
{
|
|
LOG_INFO("module.AutoBalance", "AutoBalance::isBossOrBossSummon: Creature is null.");
|
|
return false;
|
|
}
|
|
|
|
// if this creature is a boss, return true
|
|
if (creature->IsDungeonBoss() || creature->isWorldBoss())
|
|
{
|
|
if (log)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::isBossOrBossSummon: {} ({}{}) is a boss.",
|
|
creature->GetName(),
|
|
creature->GetEntry(),
|
|
creature->GetInstanceId() ? "-" + std::to_string(creature->GetInstanceId()) : ""
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// if this creature is a summon of a boss, return true
|
|
if (
|
|
creature->IsSummon() &&
|
|
creature->ToTempSummon() &&
|
|
creature->ToTempSummon()->GetSummoner() &&
|
|
creature->ToTempSummon()->GetSummoner()->ToCreature()
|
|
)
|
|
{
|
|
Creature* summoner = creature->ToTempSummon()->GetSummoner()->ToCreature();
|
|
|
|
if (summoner)
|
|
{
|
|
if (summoner->IsDungeonBoss() || summoner->isWorldBoss())
|
|
{
|
|
if (log)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::isBossOrBossSummon: {} ({}) is a summon of boss {}({}).",
|
|
creature->GetName(),
|
|
creature->GetEntry(),
|
|
summoner->GetName(),
|
|
summoner->GetEntry()
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::isBossOrBossSummon: {} ({}) is a summon of {}({}).",
|
|
creature->GetName(),
|
|
creature->GetEntry(),
|
|
summoner->GetName(),
|
|
summoner->GetEntry()
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// not a boss
|
|
if (log)
|
|
{
|
|
// LOG_DEBUG("module.AutoBalance", "AutoBalance::isBossOrBossSummon: {} ({}) is NOT a boss.",
|
|
// creature->GetName(),
|
|
// creature->GetEntry()
|
|
// );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isCreatureRelevant(Creature* creature)
|
|
{
|
|
// if the creature is gone, return false
|
|
if (!creature)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature is null.");
|
|
return false;
|
|
}
|
|
|
|
// if this creature isn't assigned to a map, make no changes
|
|
if (!creature->GetMap() || !creature->GetMap()->IsDungeon())
|
|
{
|
|
// executed every Creature update for every world creature, enable carefully
|
|
// LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) isn't in a dungeon.",
|
|
// creature->GetName(),
|
|
// creature->GetLevel()
|
|
// );
|
|
return false;
|
|
}
|
|
|
|
// get the creature's info
|
|
AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault<AutoBalanceCreatureInfo>("AutoBalanceCreatureInfo");
|
|
|
|
// if this creature has been already been evaluated, just return the previous evaluation
|
|
if (creatureABInfo->relevance == AUTOBALANCE_RELEVANCE_FALSE)
|
|
return false;
|
|
else if (creatureABInfo->relevance == AUTOBALANCE_RELEVANCE_TRUE)
|
|
return true;
|
|
// otherwise the value is AUTOBALANCE_RELEVANCE_UNCHECKED, so it needs checking
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | Needs to be evaluated.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
|
|
// get the creature's map's info
|
|
Map* creatureMap = creature->GetMap();
|
|
AutoBalanceMapInfo *mapABInfo = GetMapInfo(creatureMap);
|
|
InstanceMap* instanceMap = creatureMap->ToInstanceMap();
|
|
|
|
// if this creature is in the dungeon's base map, make no changes
|
|
if (!(instanceMap))
|
|
{
|
|
creatureABInfo->relevance = AUTOBALANCE_RELEVANCE_FALSE;
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is in the base map, no changes. Marked for skip.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// if this is a pet or summon controlled by the player, make no changes
|
|
if ((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer())
|
|
{
|
|
creatureABInfo->relevance = AUTOBALANCE_RELEVANCE_FALSE;
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is a pet or summon controlled by the player, no changes. Marked for skip.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
// if this is a player temporary summon (that isn't actively trying to kill the players), make no changes
|
|
if (
|
|
creature->ToTempSummon() &&
|
|
creature->ToTempSummon()->GetSummoner() &&
|
|
creature->ToTempSummon()->GetSummoner()->ToPlayer()
|
|
)
|
|
{
|
|
// if this creature is hostile to any non-charmed player, it should be scaled
|
|
bool isHostileToAnyValidPlayer = false;
|
|
TempSummon* creatureTempSummon = creature->ToTempSummon();
|
|
Player* summonerPlayer = creatureTempSummon->GetSummoner()->ToPlayer();
|
|
|
|
for (std::vector<Player*>::const_iterator playerIterator = mapABInfo->allMapPlayers.begin(); playerIterator != mapABInfo->allMapPlayers.end(); ++playerIterator)
|
|
{
|
|
Player* thisPlayer = *playerIterator;
|
|
|
|
// is this a valid player?
|
|
if (!thisPlayer->IsGameMaster() &&
|
|
!thisPlayer->IsCharmed() &&
|
|
!thisPlayer->IsHostileToPlayers() &&
|
|
!thisPlayer->IsHostileTo(summonerPlayer) &&
|
|
thisPlayer->IsAlive()
|
|
)
|
|
{
|
|
// if this is a guardian and the owner is not hostile to this player, skip
|
|
if
|
|
(
|
|
creatureTempSummon->IsGuardian() &&
|
|
!thisPlayer->IsHostileTo(summonerPlayer)
|
|
)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is a guardian of player {}, who is not hostile to valid player {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summonerPlayer->GetName(),
|
|
thisPlayer->GetName()
|
|
);
|
|
|
|
continue;
|
|
}
|
|
// special case for totems?
|
|
else if
|
|
(
|
|
creature->IsTotem() &&
|
|
!thisPlayer->IsHostileTo(summonerPlayer)
|
|
)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is a totem of player {}, who is not hostile to valid player {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
summonerPlayer->GetName(),
|
|
thisPlayer->GetName()
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
// if the creature is hostile to this valid player,
|
|
// unfortunately, `creature->IsHostileTo(thisPlayer)` returns true for cases when it is not actually hostile
|
|
else if (
|
|
thisPlayer->isTargetableForAttack(true, creature)
|
|
)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is a player temporary summon hostile to valid player {}.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel,
|
|
thisPlayer->GetName()
|
|
);
|
|
|
|
isHostileToAnyValidPlayer = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isHostileToAnyValidPlayer)
|
|
{
|
|
// since no players are hostile to this creature, it should not be scaled
|
|
creatureABInfo->relevance = AUTOBALANCE_RELEVANCE_FALSE;
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is player-summoned and non-hostile, no changes. Marked for skip.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// if this is a flavor critter
|
|
// level and health checks for some nasty level 1 critters in some encounters
|
|
if ((creature->IsCritter() && creatureABInfo->UnmodifiedLevel <= 5 && creature->GetMaxHealth() < 100))
|
|
{
|
|
creatureABInfo->relevance = AUTOBALANCE_RELEVANCE_FALSE;
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is a non-relevant critter, no changes. Marked for skip.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
// survived to here, creature is relevant
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::isCreatureRelevant: Creature {} ({}) | is relevant. Marked for processing.",
|
|
creature->GetName(),
|
|
creatureABInfo->UnmodifiedLevel
|
|
);
|
|
creatureABInfo->relevance = AUTOBALANCE_RELEVANCE_TRUE;
|
|
return true;
|
|
|
|
}
|
|
|
|
bool isDungeonInMinPlayerMap(uint32 dungeonId, bool isHeroic)
|
|
{
|
|
if (isHeroic)
|
|
return (minPlayersPerHeroicDungeonIdMap.find(dungeonId) != minPlayersPerHeroicDungeonIdMap.end());
|
|
else
|
|
return (minPlayersPerDungeonIdMap.find(dungeonId) != minPlayersPerDungeonIdMap.end());
|
|
}
|
|
|
|
// Used for reading the string from the configuration file to for those creatures who need to be scaled for XX number of players.
|
|
//
|
|
void LoadForcedCreatureIdsFromString(std::string creatureIds, int forcedPlayerCount)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream creatureIdsStream;
|
|
|
|
creatureIdsStream.str(creatureIds);
|
|
|
|
while (std::getline(creatureIdsStream, delimitedValue, ',')) // Process each Creature ID in the string, delimited by the comma - ","
|
|
{
|
|
int creatureId = atoi(delimitedValue.c_str());
|
|
|
|
if (creatureId >= 0)
|
|
forcedCreatureIds[creatureId] = forcedPlayerCount;
|
|
}
|
|
}
|
|
|
|
// Used for reading the string from the configuration file for selectively disabling dungeons
|
|
//
|
|
std::list<uint32> LoadDisabledDungeons(std::string dungeonIdString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
std::list<uint32> dungeonIdList;
|
|
|
|
dungeonIdStream.str(dungeonIdString);
|
|
|
|
// Process each dungeon ID in the string, delimited by the comma - ","
|
|
//
|
|
while (std::getline(dungeonIdStream, delimitedValue, ','))
|
|
{
|
|
std::string valueOne;
|
|
std::stringstream dungeonPairStream(delimitedValue);
|
|
|
|
dungeonPairStream >> valueOne;
|
|
auto dungeonMapId = atoi(valueOne.c_str());
|
|
|
|
dungeonIdList.push_back(dungeonMapId);
|
|
}
|
|
|
|
return dungeonIdList;
|
|
}
|
|
|
|
std::map<uint32, uint32> LoadDistanceCheckOverrides(std::string dungeonIdString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
|
|
std::map<uint32, uint32> overrideMap;
|
|
|
|
dungeonIdStream.str(dungeonIdString);
|
|
|
|
// Process each dungeon ID in the string, delimited by the comma - "," and then space " "
|
|
//
|
|
while (std::getline(dungeonIdStream, delimitedValue, ','))
|
|
{
|
|
std::string val1;
|
|
std::string val2;
|
|
std::stringstream dungeonStream(delimitedValue);
|
|
|
|
dungeonStream >> val1 >> val2;
|
|
|
|
auto dungeonMapId = atoi(val1.c_str());
|
|
overrideMap[dungeonMapId] = atoi(val2.c_str());
|
|
}
|
|
|
|
return overrideMap;
|
|
}
|
|
|
|
|
|
// Used for reading the string from the configuration file for per-dungeon dynamic level overrides
|
|
//
|
|
std::map<uint8, AutoBalanceLevelScalingDynamicLevelSettings> LoadDynamicLevelOverrides(std::string dungeonIdString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
|
|
std::map<uint8, AutoBalanceLevelScalingDynamicLevelSettings> overrideMap;
|
|
|
|
dungeonIdStream.str(dungeonIdString);
|
|
|
|
// Process each dungeon ID in the string, delimited by the comma - "," and then space " "
|
|
//
|
|
while (std::getline(dungeonIdStream, delimitedValue, ','))
|
|
{
|
|
std::string val1;
|
|
std::string val2;
|
|
std::string val3;
|
|
std::string val4;
|
|
std::string val5;
|
|
std::stringstream dungeonStream(delimitedValue);
|
|
|
|
dungeonStream >> val1 >> val2 >> val3 >> val4 >> val5;
|
|
auto dungeonMapId = atoi(val1.c_str());
|
|
|
|
// Replace any missing values with -1
|
|
if (val2.empty())
|
|
val2 = "-1";
|
|
if (val3.empty())
|
|
val3 = "-1";
|
|
if (val4.empty())
|
|
val4 = "-1";
|
|
if (val5.empty())
|
|
val5 = "-1";
|
|
|
|
AutoBalanceLevelScalingDynamicLevelSettings dynamicLevelSettings = AutoBalanceLevelScalingDynamicLevelSettings(
|
|
atoi(val2.c_str()),
|
|
atoi(val3.c_str()),
|
|
atoi(val4.c_str()),
|
|
atoi(val5.c_str())
|
|
);
|
|
|
|
overrideMap[dungeonMapId] = dynamicLevelSettings;
|
|
}
|
|
|
|
return overrideMap;
|
|
}
|
|
|
|
|
|
// Used for reading the string from the configuration file for selecting dungeons to override
|
|
//
|
|
std::map<uint32, AutoBalanceInflectionPointSettings> LoadInflectionPointOverrides(std::string dungeonIdString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
|
|
std::map<uint32, AutoBalanceInflectionPointSettings> overrideMap;
|
|
|
|
dungeonIdStream.str(dungeonIdString);
|
|
|
|
while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " "
|
|
{
|
|
std::string val1;
|
|
std::string val2;
|
|
std::string val3;
|
|
std::string val4;
|
|
std::stringstream dungeonPairStream(delimitedValue);
|
|
|
|
dungeonPairStream >> val1 >> val2 >> val3 >> val4;
|
|
auto dungeonMapId = atoi(val1.c_str());
|
|
|
|
// Replace any missing values with -1
|
|
if (val2.empty())
|
|
val2 = "-1";
|
|
if (val3.empty())
|
|
val3 = "-1";
|
|
if (val4.empty())
|
|
val4 = "-1";
|
|
|
|
AutoBalanceInflectionPointSettings ipSettings = AutoBalanceInflectionPointSettings(
|
|
atof(val2.c_str()),
|
|
atof(val3.c_str()),
|
|
atof(val4.c_str())
|
|
);
|
|
|
|
overrideMap[dungeonMapId] = ipSettings;
|
|
}
|
|
|
|
return overrideMap;
|
|
}
|
|
|
|
void LoadMapSettings(Map* map)
|
|
{
|
|
//
|
|
// Load (or create) the map's info
|
|
//
|
|
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(map);
|
|
|
|
//
|
|
// Create an InstanceMap object
|
|
//
|
|
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::LoadMapSettings: Map {} ({}{}, {}-player {}) | Loading settings.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
|
|
//
|
|
// Determine the minumum player count
|
|
//
|
|
|
|
if (isDungeonInMinPlayerMap(map->GetId(), instanceMap->IsHeroic()))
|
|
mapABInfo->minPlayers = instanceMap->IsHeroic() ? minPlayersPerHeroicDungeonIdMap[map->GetId()] : minPlayersPerDungeonIdMap[map->GetId()];
|
|
else if (instanceMap->GetMaxPlayers() <= 5 && !instanceMap->IsHeroic())
|
|
mapABInfo->minPlayers = minPlayersNormal;
|
|
else if (instanceMap->GetMaxPlayers() <= 5 && instanceMap->IsHeroic())
|
|
mapABInfo->minPlayers = minPlayersHeroic;
|
|
else if (instanceMap->GetMaxPlayers() > 5 && !instanceMap->IsHeroic())
|
|
mapABInfo->minPlayers = minPlayersRaid;
|
|
else
|
|
mapABInfo->minPlayers = minPlayersRaidHeroic;
|
|
|
|
//
|
|
// If the minPlayers value we determined is less than the max number of players in this map, adjust down
|
|
//
|
|
if (mapABInfo->minPlayers > instanceMap->GetMaxPlayers())
|
|
{
|
|
LOG_WARN("module.AutoBalance", "AutoBalance::LoadMapSettings: Your settings tried to set a minimum player count of {} which is greater than {}'s max player count of {}. Adjusting down.",
|
|
mapABInfo->minPlayers,
|
|
map->GetMapName(),
|
|
instanceMap->GetMaxPlayers()
|
|
);
|
|
|
|
mapABInfo->minPlayers = instanceMap->GetMaxPlayers();
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::LoadMapSettings: Map {} ({}{}, {}-player {}) | has a minimum player count of {}.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal",
|
|
mapABInfo->minPlayers
|
|
);
|
|
|
|
//
|
|
// Dynamic Level Scaling Floor and Ceiling
|
|
//
|
|
|
|
// 5-player normal dungeons
|
|
if (instanceMap->GetMaxPlayers() <= 5 && !instanceMap->IsHeroic())
|
|
{
|
|
mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingDungeons;
|
|
mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorDungeons;
|
|
|
|
}
|
|
// 5-player heroic dungeons
|
|
else if (instanceMap->GetMaxPlayers() <= 5 && instanceMap->IsHeroic())
|
|
{
|
|
mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingHeroicDungeons;
|
|
mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorHeroicDungeons;
|
|
}
|
|
// Normal raids
|
|
else if (instanceMap->GetMaxPlayers() > 5 && !instanceMap->IsHeroic())
|
|
{
|
|
mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingRaids;
|
|
mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorRaids;
|
|
}
|
|
// Heroic raids
|
|
else if (instanceMap->GetMaxPlayers() > 5 && instanceMap->IsHeroic())
|
|
{
|
|
mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingHeroicRaids;
|
|
mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorHeroicRaids;
|
|
}
|
|
// something went wrong
|
|
else
|
|
{
|
|
LOG_ERROR("module.AutoBalance", "AutoBalance::LoadMapSettings: Map {} ({}{}, {}-player {}) | Unable to determine dynamic scaling floor and ceiling for instance.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
|
|
mapABInfo->levelScalingDynamicCeiling = 3;
|
|
mapABInfo->levelScalingDynamicFloor = 5;
|
|
|
|
}
|
|
|
|
//
|
|
// Level Scaling Skip Levels
|
|
//
|
|
|
|
// Load the global settings into the map
|
|
mapABInfo->levelScalingSkipHigherLevels = LevelScalingSkipHigherLevels;
|
|
mapABInfo->levelScalingSkipLowerLevels = LevelScalingSkipLowerLevels;
|
|
|
|
//
|
|
// Per-instance overrides, if applicable
|
|
//
|
|
|
|
if (hasDynamicLevelOverride(map->GetId()))
|
|
{
|
|
AutoBalanceLevelScalingDynamicLevelSettings* myDynamicLevelSettings = &levelScalingDynamicLevelOverrides[map->GetId()];
|
|
|
|
// LevelScaling.SkipHigherLevels
|
|
if (myDynamicLevelSettings->skipHigher != -1)
|
|
mapABInfo->levelScalingSkipHigherLevels = myDynamicLevelSettings->skipHigher;
|
|
|
|
// LevelScaling.SkipLowerLevels
|
|
if (myDynamicLevelSettings->skipLower != -1)
|
|
mapABInfo->levelScalingSkipLowerLevels = myDynamicLevelSettings->skipLower;
|
|
|
|
// LevelScaling.DynamicLevelCeiling
|
|
if (myDynamicLevelSettings->ceiling != -1)
|
|
mapABInfo->levelScalingDynamicCeiling = myDynamicLevelSettings->ceiling;
|
|
|
|
// LevelScaling.DynamicLevelFloor
|
|
if (myDynamicLevelSettings->floor != -1)
|
|
mapABInfo->levelScalingDynamicFloor = myDynamicLevelSettings->floor;
|
|
}
|
|
}
|
|
|
|
// Used for reading the string from the configuration file for per-dungeon minimum player count overrides
|
|
//
|
|
std::map<uint32, uint8> LoadMinPlayersPerDungeonId(std::string minPlayersString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
std::map<uint32, uint8> dungeonIdMap;
|
|
|
|
dungeonIdStream.str(minPlayersString);
|
|
|
|
// Process each dungeon ID in the string, delimited by the comma - "," and then space " "
|
|
//
|
|
while (std::getline(dungeonIdStream, delimitedValue, ','))
|
|
{
|
|
std::string val1;
|
|
std::string val2;
|
|
std::stringstream dungeonPairStream(delimitedValue);
|
|
|
|
dungeonPairStream >> val1 >> val2;
|
|
auto dungeonMapId = atoi(val1.c_str());
|
|
auto minPlayers = atoi(val2.c_str());
|
|
|
|
dungeonIdMap[dungeonMapId] = minPlayers;
|
|
}
|
|
|
|
return dungeonIdMap;
|
|
}
|
|
|
|
// Used for reading the string from the configuration file for per-dungeon stat modifiers
|
|
//
|
|
std::map<uint32, AutoBalanceStatModifiers> LoadStatModifierOverrides(std::string dungeonIdString)
|
|
{
|
|
std::string delimitedValue;
|
|
std::stringstream dungeonIdStream;
|
|
|
|
std::map<uint32, AutoBalanceStatModifiers> overrideMap;
|
|
|
|
dungeonIdStream.str(dungeonIdString);
|
|
|
|
// Process each dungeon ID in the string, delimited by the comma - "," and then space " "
|
|
//
|
|
while (std::getline(dungeonIdStream, delimitedValue, ','))
|
|
{
|
|
std::string val1;
|
|
std::string val2;
|
|
std::string val3;
|
|
std::string val4;
|
|
std::string val5;
|
|
std::string val6;
|
|
std::string val7;
|
|
std::stringstream dungeonStream(delimitedValue);
|
|
|
|
dungeonStream >> val1 >> val2 >> val3 >> val4 >> val5 >> val6 >> val7;
|
|
auto dungeonMapId = atoi(val1.c_str());
|
|
|
|
// Replace any missing values with -1
|
|
if (val2.empty())
|
|
val2 = "-1";
|
|
if (val3.empty())
|
|
val3 = "-1";
|
|
if (val4.empty())
|
|
val4 = "-1";
|
|
if (val5.empty())
|
|
val5 = "-1";
|
|
if (val6.empty())
|
|
val6 = "-1";
|
|
if (val7.empty())
|
|
val7 = "-1";
|
|
|
|
AutoBalanceStatModifiers statSettings = AutoBalanceStatModifiers(
|
|
atof(val2.c_str()),
|
|
atof(val3.c_str()),
|
|
atof(val4.c_str()),
|
|
atof(val5.c_str()),
|
|
atof(val6.c_str()),
|
|
atof(val7.c_str())
|
|
);
|
|
|
|
overrideMap[dungeonMapId] = statSettings;
|
|
}
|
|
|
|
return overrideMap;
|
|
}
|
|
|
|
bool ShouldMapBeEnabled(Map* map)
|
|
{
|
|
if (map->IsDungeon())
|
|
{
|
|
// if globally disabled, return false
|
|
if (!EnableGlobal)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: {} ({}{}) - Not enabled because EnableGlobal is false",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : ""
|
|
);
|
|
return false;
|
|
}
|
|
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
|
|
// if there wasn't one, then we're not in an instance
|
|
if (!instanceMap)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: {} ({}{}) - Not enabled for the base map without an Instance ID.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : ""
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// if the player count is less than 1, then we're not in an instance
|
|
if (instanceMap->GetMaxPlayers() < 1)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: {} ({}{}, {}-player {}) - Not enabled because GetMaxPlayers < 1",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// if the Dungeon is disabled via configuration, do not enable it
|
|
if (isDungeonInDisabledDungeonIds(map->GetId()))
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: {} ({}{}, {}-player {}) - Not enabled because the map ID is disabled via configuration.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
// use the configuration variables to determine if this instance type/size should have scaling enabled
|
|
bool sizeDifficultyEnabled;
|
|
if (instanceMap->IsHeroic())
|
|
{
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: Heroic Enables - 5:{} 10:{} 25:{} Other:{}",
|
|
// Enable5MHeroic, Enable10MHeroic, Enable25MHeroic, EnableOtherHeroic);
|
|
|
|
if (instanceMap->GetMaxPlayers() <= 5)
|
|
sizeDifficultyEnabled = Enable5MHeroic;
|
|
else if (instanceMap->GetMaxPlayers() <= 10)
|
|
sizeDifficultyEnabled = Enable10MHeroic;
|
|
else if (instanceMap->GetMaxPlayers() <= 25)
|
|
sizeDifficultyEnabled = Enable25MHeroic;
|
|
else
|
|
sizeDifficultyEnabled = EnableOtherHeroic;
|
|
}
|
|
else
|
|
{
|
|
//LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: Normal Enables - 5:{} 10:{} 15:{} 20:{} 25:{} 40:{} Other:{}",
|
|
// Enable5M, Enable10M, Enable15M, Enable20M, Enable25M, Enable40M, EnableOtherNormal);
|
|
if (instanceMap->GetMaxPlayers() <= 5)
|
|
sizeDifficultyEnabled = Enable5M;
|
|
else if (instanceMap->GetMaxPlayers() <= 10)
|
|
sizeDifficultyEnabled = Enable10M;
|
|
else if (instanceMap->GetMaxPlayers() <= 15)
|
|
sizeDifficultyEnabled = Enable15M;
|
|
else if (instanceMap->GetMaxPlayers() <= 20)
|
|
sizeDifficultyEnabled = Enable20M;
|
|
else if (instanceMap->GetMaxPlayers() <= 25)
|
|
sizeDifficultyEnabled = Enable25M;
|
|
else if (instanceMap->GetMaxPlayers() <= 40)
|
|
sizeDifficultyEnabled = Enable40M;
|
|
else
|
|
sizeDifficultyEnabled = EnableOtherNormal;
|
|
}
|
|
|
|
if (sizeDifficultyEnabled)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: Map {} ({}{}, {}-player {}) | Enabled for AutoBalancing.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: Map {} ({}{}, {}-player {}) | Not enabled because its size and difficulty are disabled via configuration.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal"
|
|
);
|
|
}
|
|
|
|
return sizeDifficultyEnabled;
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::ShouldMapBeEnabled: Map {} ({}{}) | Not enabled because the map is not an instance.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : ""
|
|
);
|
|
return false;
|
|
|
|
// we're not in a dungeon or a raid, we never scale
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void UpdateMapPlayerStats(Map* map)
|
|
{
|
|
//
|
|
// If this isn't a dungeon instance, just bail out immediately
|
|
//
|
|
|
|
if (!map->IsDungeon() || !map->GetInstanceId())
|
|
return;
|
|
|
|
//
|
|
// Get the map's info
|
|
//
|
|
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(map);
|
|
InstanceMap* instanceMap = map->ToInstanceMap();
|
|
|
|
//
|
|
// Remember some values
|
|
//
|
|
|
|
uint8 oldPlayerCount = mapABInfo->playerCount;
|
|
uint8 oldAdjustedPlayerCount = mapABInfo->adjustedPlayerCount;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | oldPlayerCount = ({}), oldAdjustedPlayerCount = ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
oldPlayerCount,
|
|
oldAdjustedPlayerCount);
|
|
|
|
//
|
|
// Update the player count
|
|
// Minimum of 1 to prevent scaling weirdness when only GMs are in the instnace
|
|
//
|
|
|
|
mapABInfo->playerCount = mapABInfo->allMapPlayers.size() ? mapABInfo->allMapPlayers.size() : 1;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | playerCount = ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
mapABInfo->playerCount);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | combatLocked = ({}), combatLockMinPlayers = ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
mapABInfo->combatLocked,
|
|
mapABInfo->combatLockMinPlayers);
|
|
|
|
uint8 adjustedPlayerCount = 0;
|
|
|
|
//
|
|
// If combat is locked and the new player count is higher than the combat lock, update the combat lock
|
|
//
|
|
|
|
if(mapABInfo->combatLocked &&
|
|
mapABInfo->playerCount > oldPlayerCount &&
|
|
mapABInfo->playerCount > mapABInfo->combatLockMinPlayers)
|
|
{
|
|
//
|
|
// Start with the actual player count
|
|
//
|
|
|
|
adjustedPlayerCount = mapABInfo->playerCount;
|
|
|
|
//
|
|
// This is the new floor
|
|
//
|
|
|
|
mapABInfo->combatLockMinPlayers = mapABInfo->playerCount;
|
|
|
|
LOG_DEBUG("module.AutoBalance_CombatLocking", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | Combat is locked. Combat floor increased. New floor is ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
mapABInfo->combatLockMinPlayers);
|
|
|
|
}
|
|
//
|
|
// If combat is otherwise locked
|
|
//
|
|
else if (mapABInfo->combatLocked)
|
|
{
|
|
//
|
|
// Start with the saved floor
|
|
//
|
|
adjustedPlayerCount = mapABInfo->combatLockMinPlayers ? mapABInfo->combatLockMinPlayers : mapABInfo->playerCount;
|
|
|
|
LOG_DEBUG("module.AutoBalance_CombatLocking", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | Combat is locked. Combat floor is ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
mapABInfo->combatLockMinPlayers);
|
|
}
|
|
//
|
|
// If combat is not locked
|
|
//
|
|
else
|
|
{
|
|
//
|
|
// Start with the actual player count
|
|
//
|
|
adjustedPlayerCount = mapABInfo->playerCount;
|
|
}
|
|
|
|
//
|
|
// If the adjusted player count is below the min players setting, adjust it
|
|
//
|
|
|
|
if (adjustedPlayerCount < mapABInfo->minPlayers)
|
|
adjustedPlayerCount = mapABInfo->minPlayers;
|
|
|
|
//
|
|
// Adjust by the PlayerDifficultyOffset
|
|
//
|
|
|
|
adjustedPlayerCount += PlayerCountDifficultyOffset;
|
|
|
|
//
|
|
// Store the adjusted player count in the map's info
|
|
//
|
|
|
|
mapABInfo->adjustedPlayerCount = adjustedPlayerCount;
|
|
|
|
//
|
|
// If the adjustedPlayerCount changed, schedule this map for a reconfiguration
|
|
//
|
|
|
|
if (oldAdjustedPlayerCount != mapABInfo->adjustedPlayerCount)
|
|
{
|
|
mapABInfo->mapConfigTime = 1;
|
|
|
|
LOG_DEBUG("module.AutoBalance_CombatLocking", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}) | Player difficulty changes ({}->{}). Force map update. {} ({}{}) map config time set to ({}).",
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
oldAdjustedPlayerCount,
|
|
mapABInfo->adjustedPlayerCount,
|
|
instanceMap->GetMapName(),
|
|
instanceMap->GetId(),
|
|
instanceMap->GetInstanceId() ? "-" + std::to_string(instanceMap->GetInstanceId()) : "",
|
|
mapABInfo->mapConfigTime);
|
|
}
|
|
|
|
uint8 highestPlayerLevel = 0;
|
|
uint8 lowestPlayerLevel = 80;
|
|
|
|
//
|
|
// Iterate through the players and update the highest and lowest player levels
|
|
//
|
|
|
|
for (std::vector<Player*>::const_iterator playerIterator = mapABInfo->allMapPlayers.begin(); playerIterator != mapABInfo->allMapPlayers.end(); ++playerIterator)
|
|
{
|
|
Player* thisPlayer = *playerIterator;
|
|
|
|
if (thisPlayer && !thisPlayer->IsGameMaster())
|
|
{
|
|
if (thisPlayer->GetLevel() > highestPlayerLevel || highestPlayerLevel == 0)
|
|
highestPlayerLevel = thisPlayer->GetLevel();
|
|
|
|
if (thisPlayer->GetLevel() < lowestPlayerLevel || lowestPlayerLevel == 0)
|
|
lowestPlayerLevel = thisPlayer->GetLevel();
|
|
}
|
|
}
|
|
|
|
mapABInfo->highestPlayerLevel = highestPlayerLevel;
|
|
mapABInfo->lowestPlayerLevel = lowestPlayerLevel;
|
|
|
|
if (!highestPlayerLevel)
|
|
{
|
|
mapABInfo->highestPlayerLevel = mapABInfo->lfgTargetLevel;
|
|
mapABInfo->lowestPlayerLevel = mapABInfo->lfgTargetLevel;
|
|
|
|
//
|
|
// No non-GM players on the map
|
|
//
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}, {}-player {}) | has no non-GM players. Player stats derived from LFG target level.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal");
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapPlayerStats: Map {} ({}{}, {}-player {}) | has {} player(s) with level range ({})-({}). Difficulty is {} player(s).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
instanceMap->GetMaxPlayers(),
|
|
instanceMap->IsHeroic() ? "Heroic" : "Normal",
|
|
mapABInfo->playerCount,
|
|
mapABInfo->lowestPlayerLevel,
|
|
mapABInfo->highestPlayerLevel,
|
|
mapABInfo->adjustedPlayerCount);
|
|
}
|
|
}
|
|
|
|
void AddPlayerToMap(Map* map, Player* player)
|
|
{
|
|
//
|
|
// Get map data
|
|
//
|
|
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(map);
|
|
|
|
|
|
if (!player)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddPlayerToMap: Map {} ({}{}) | Player does not exist.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "");
|
|
return;
|
|
}
|
|
//
|
|
// Player is a GM
|
|
//
|
|
else if (player->IsGameMaster())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddPlayerToMap: Map {} ({}{}) | Game Master ({}) will not be added to the player list.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
player->GetName());
|
|
return;
|
|
}
|
|
//
|
|
// If this player is already in the map's player list, skip
|
|
//
|
|
else if (std::find(mapABInfo->allMapPlayers.begin(), mapABInfo->allMapPlayers.end(), player) != mapABInfo->allMapPlayers.end())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddPlayerToMap: Player {} ({}) | is already in the map's player list.",
|
|
player->GetName(),
|
|
player->GetLevel());
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Add the player to the map's player list
|
|
//
|
|
|
|
mapABInfo->allMapPlayers.push_back(player);
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::AddPlayerToMap: Player {} ({}) | added to the map's player list.", player->GetName(), player->GetLevel());
|
|
|
|
//
|
|
// Update the map's player stats
|
|
//
|
|
|
|
UpdateMapPlayerStats(map);
|
|
}
|
|
|
|
bool RemovePlayerFromMap(Map* map, Player* player)
|
|
{
|
|
//
|
|
// Get map data
|
|
//
|
|
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(map);
|
|
|
|
//
|
|
// If this player isn't in the map's player list, skip
|
|
//
|
|
|
|
if (std::find(mapABInfo->allMapPlayers.begin(), mapABInfo->allMapPlayers.end(), player) == mapABInfo->allMapPlayers.end())
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::RemovePlayerFromMap: Player {} ({}) | was not in the map's player list.", player->GetName(), player->GetLevel());
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Remove the player from the map's player list
|
|
//
|
|
|
|
mapABInfo->allMapPlayers.erase(std::remove(mapABInfo->allMapPlayers.begin(), mapABInfo->allMapPlayers.end(), player), mapABInfo->allMapPlayers.end());
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::RemovePlayerFromMap: Player {} ({}) | removed from the map's player list.", player->GetName(), player->GetLevel());
|
|
|
|
//
|
|
// If the map is combat locked, schedule a map update for when combat ends
|
|
//
|
|
|
|
if (mapABInfo->combatLocked)
|
|
mapABInfo->combatLockTripped = true;
|
|
|
|
//
|
|
// Update the map's player stats
|
|
//
|
|
|
|
UpdateMapPlayerStats(map);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UpdateMapDataIfNeeded(Map* map, bool force)
|
|
{
|
|
//
|
|
// Get map data
|
|
//
|
|
|
|
AutoBalanceMapInfo* mapABInfo = GetMapInfo(map);
|
|
|
|
//
|
|
// If map needs update
|
|
//
|
|
|
|
if (force || mapABInfo->globalConfigTime < globalConfigTime || mapABInfo->mapConfigTime < mapABInfo->globalConfigTime)
|
|
{
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | globalConfigTime = ({}) | mapConfigTime = ({})",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->globalConfigTime,
|
|
mapABInfo->mapConfigTime);
|
|
|
|
//
|
|
// Update forced
|
|
//
|
|
|
|
if (force)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Update forced.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->mapConfigTime);
|
|
}
|
|
|
|
//
|
|
// Some tracking variables
|
|
//
|
|
|
|
bool isGlobalConfigOutOfDate = mapABInfo->globalConfigTime < globalConfigTime;
|
|
bool isMapConfigOutOfDate = mapABInfo->mapConfigTime < globalConfigTime;
|
|
|
|
//
|
|
// If this was triggered by a global config update, redetect players
|
|
//
|
|
|
|
if (isGlobalConfigOutOfDate)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Global config is out of date ({} < {}) and will be updated.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->globalConfigTime,
|
|
globalConfigTime);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Will recount players in the map.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "");
|
|
|
|
//
|
|
// Clear the map's player list
|
|
//
|
|
|
|
mapABInfo->allMapPlayers.clear();
|
|
|
|
//
|
|
// Reset the combat lock
|
|
//
|
|
|
|
mapABInfo->combatLockMinPlayers = 0;
|
|
|
|
//
|
|
// Get the map's player list
|
|
//
|
|
|
|
Map::PlayerList const& playerList = map->GetPlayers();
|
|
|
|
//
|
|
// Re-count the players in the dungeon
|
|
//
|
|
|
|
for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration)
|
|
{
|
|
Player* thisPlayer = playerIteration->GetSource();
|
|
|
|
//
|
|
// If the player is in combat, combat lock the map
|
|
//
|
|
|
|
if (thisPlayer->IsInCombat())
|
|
{
|
|
mapABInfo->combatLocked = true;
|
|
|
|
LOG_DEBUG("module.AutoBalance_CombatLocking", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Player {} is in combat. Map is combat locked.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
thisPlayer->GetName());
|
|
}
|
|
|
|
//
|
|
// (conditionally) add the player to the map's player list
|
|
//
|
|
|
|
AddPlayerToMap(map, thisPlayer);
|
|
}
|
|
|
|
//
|
|
// Map's player count will be updated in UpdateMapPlayerStats below
|
|
//
|
|
}
|
|
|
|
//
|
|
// Map config is out of date
|
|
//
|
|
|
|
if (isMapConfigOutOfDate)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Map config is out of date ({} < {}) and will be updated.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->mapConfigTime,
|
|
globalConfigTime);
|
|
}
|
|
|
|
//
|
|
// Should the map be enabled?
|
|
//
|
|
|
|
bool newEnabled = ShouldMapBeEnabled(map);
|
|
|
|
//
|
|
// If this is a transition between enabled states, reset the map's config time so it will refresh
|
|
///
|
|
|
|
if (mapABInfo->enabled != newEnabled)
|
|
{
|
|
mapABInfo->mapConfigTime = 1;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | Enabled state transitions from {}->{}, map update forced. Map config time set to ({}).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->enabled ? "ENABLED" : "DISABLED",
|
|
newEnabled ? "ENABLED" : "DISABLED",
|
|
mapABInfo->mapConfigTime);
|
|
}
|
|
|
|
//
|
|
// Update the enabled state
|
|
//
|
|
|
|
mapABInfo->enabled = newEnabled;
|
|
|
|
if (!mapABInfo->enabled)
|
|
{
|
|
//
|
|
// Mark the config updated to prevent checking the disabled map repeatedly
|
|
//
|
|
|
|
mapABInfo->globalConfigTime = globalConfigTime;
|
|
mapABInfo->mapConfigTime = globalConfigTime;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) | is disabled.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "");
|
|
}
|
|
|
|
//
|
|
// Load the map's settings
|
|
//
|
|
|
|
LoadMapSettings(map);
|
|
|
|
//
|
|
// Update the map's player stats
|
|
//
|
|
|
|
UpdateMapPlayerStats(map);
|
|
|
|
//
|
|
// If LevelScaling is disabled OR if the average creature level is inside the skip range,
|
|
// Set the map level to the average creature level, rounded to the nearest integer
|
|
//
|
|
|
|
if (!LevelScaling ||
|
|
((mapABInfo->avgCreatureLevel <= mapABInfo->highestPlayerLevel + mapABInfo->levelScalingSkipHigherLevels && mapABInfo->levelScalingSkipHigherLevels != 0) &&
|
|
(mapABInfo->avgCreatureLevel >= mapABInfo->highestPlayerLevel - mapABInfo->levelScalingSkipLowerLevels && mapABInfo->levelScalingSkipLowerLevels != 0)))
|
|
{
|
|
mapABInfo->prevMapLevel = mapABInfo->mapLevel;
|
|
mapABInfo->mapLevel = (uint8)(mapABInfo->avgCreatureLevel + 0.5f);
|
|
mapABInfo->isLevelScalingEnabled = false;
|
|
|
|
//
|
|
// Only log if the mapLevel has changed
|
|
//
|
|
|
|
if (mapABInfo->prevMapLevel != mapABInfo->mapLevel)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}, {}-player {}) | Level scaling is disabled. Map level tracking stat updated {}{} (original level).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
map->ToInstanceMap()->GetMaxPlayers(),
|
|
map->ToInstanceMap()->IsHeroic() ? "Heroic" : "Normal",
|
|
mapABInfo->mapLevel != mapABInfo->prevMapLevel ? std::to_string(mapABInfo->prevMapLevel) + "->" : "",
|
|
mapABInfo->mapLevel);
|
|
}
|
|
|
|
}
|
|
//
|
|
// If the average creature level is lower than the highest player level,
|
|
// Set the map level to the average creature level, rounded to the nearest integer
|
|
//
|
|
else if (mapABInfo->avgCreatureLevel <= mapABInfo->highestPlayerLevel)
|
|
{
|
|
mapABInfo->prevMapLevel = mapABInfo->mapLevel;
|
|
mapABInfo->mapLevel = (uint8)(mapABInfo->avgCreatureLevel + 0.5f);
|
|
mapABInfo->isLevelScalingEnabled = true;
|
|
|
|
//
|
|
// Only log if the mapLevel has changed
|
|
//
|
|
if (mapABInfo->prevMapLevel != mapABInfo->mapLevel)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}, {}-player {}) | Level scaling is enabled. Map level updated ({}{}) (average creature level).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
map->ToInstanceMap()->GetMaxPlayers(),
|
|
map->ToInstanceMap()->IsHeroic() ? "Heroic" : "Normal",
|
|
mapABInfo->mapLevel != mapABInfo->prevMapLevel ? std::to_string(mapABInfo->prevMapLevel) + "->" : "",
|
|
mapABInfo->mapLevel);
|
|
}
|
|
}
|
|
//
|
|
// Caps at the highest player level
|
|
//
|
|
else
|
|
{
|
|
mapABInfo->prevMapLevel = mapABInfo->mapLevel;
|
|
mapABInfo->mapLevel = mapABInfo->highestPlayerLevel;
|
|
mapABInfo->isLevelScalingEnabled = true;
|
|
|
|
//
|
|
// Only log if the mapLevel has changed
|
|
//
|
|
if (mapABInfo->prevMapLevel != mapABInfo->mapLevel)
|
|
{
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}, {}-player {}) | Lcaling is enabled. Map level updated ({}{}) (highest player level).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
map->ToInstanceMap()->GetMaxPlayers(),
|
|
map->ToInstanceMap()->IsHeroic() ? "Heroic" : "Normal",
|
|
mapABInfo->mapLevel != mapABInfo->prevMapLevel ? std::to_string(mapABInfo->prevMapLevel) + "->" : "",
|
|
mapABInfo->mapLevel);
|
|
}
|
|
}
|
|
|
|
//
|
|
// World multipliers only need to be updated if the mapLevel has changed OR if the map config is out of date
|
|
//
|
|
|
|
if (mapABInfo->prevMapLevel != mapABInfo->mapLevel || isMapConfigOutOfDate)
|
|
{
|
|
//
|
|
// Update World Health multiplier
|
|
// Used for scaling damage against destructible game objects
|
|
//
|
|
|
|
World_Multipliers health = getWorldMultiplier(map, BaseValueType::AUTOBALANCE_HEALTH);
|
|
|
|
mapABInfo->worldHealthMultiplier = health.unscaled;
|
|
|
|
//
|
|
// Update World Damage or Healing multiplier
|
|
// Used for scaling damage and healing between players and/or non-creatures
|
|
//
|
|
|
|
World_Multipliers damageHealing = getWorldMultiplier(map, BaseValueType::AUTOBALANCE_DAMAGE_HEALING);
|
|
|
|
mapABInfo->worldDamageHealingMultiplier = damageHealing.unscaled;
|
|
mapABInfo->scaledWorldDamageHealingMultiplier = damageHealing.scaled;
|
|
}
|
|
|
|
//
|
|
// Mark the config updated
|
|
//
|
|
|
|
mapABInfo->globalConfigTime = globalConfigTime;
|
|
mapABInfo->mapConfigTime = GetCurrentConfigTime();
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: {} ({}{}) | Global config time set to ({}).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->globalConfigTime);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: {} ({}{}) | Map config time set to ({}).",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->mapConfigTime);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// LOG_DEBUG("module.AutoBalance", "AutoBalance::UpdateMapDataIfNeeded: Map {} ({}{}) global config is up to date ({} == {}).",
|
|
// map->GetMapName(),
|
|
// map->GetId(),
|
|
// map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
// mapABInfo->globalConfigTime,
|
|
// globalConfigTime
|
|
// );
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AutoBalanceMapInfo* GetMapInfo(Map* map)
|
|
{
|
|
AutoBalanceMapInfo* mapABInfo = map->CustomData.GetDefault<AutoBalanceMapInfo>("AutoBalanceMapInfo");
|
|
if (mapABInfo->initialized)
|
|
return mapABInfo;
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::InitializeMap: Map {} ({}{}) | Initializing map.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "");
|
|
|
|
mapABInfo->initialized = true;
|
|
|
|
if (!map->IsDungeon())
|
|
return mapABInfo;
|
|
|
|
// get the map's LFG stats even if not enabled
|
|
LFGDungeonEntry const* dungeon = GetLFGDungeon(map->GetId(), map->GetDifficulty());
|
|
if (dungeon) {
|
|
mapABInfo->lfgMinLevel = dungeon->MinLevel;
|
|
mapABInfo->lfgMaxLevel = dungeon->MaxLevel;
|
|
mapABInfo->lfgTargetLevel = dungeon->TargetLevel;
|
|
}
|
|
// if this is a heroic dungeon that isn't in LFG, get the stats from the non-heroic version
|
|
else if (map->IsHeroic())
|
|
{
|
|
LFGDungeonEntry const* nonHeroicDungeon = nullptr;
|
|
if (map->GetDifficulty() == DUNGEON_DIFFICULTY_HEROIC)
|
|
nonHeroicDungeon = GetLFGDungeon(map->GetId(), DUNGEON_DIFFICULTY_NORMAL);
|
|
else if (map->GetDifficulty() == RAID_DIFFICULTY_10MAN_HEROIC)
|
|
nonHeroicDungeon = GetLFGDungeon(map->GetId(), RAID_DIFFICULTY_10MAN_NORMAL);
|
|
else if (map->GetDifficulty() == RAID_DIFFICULTY_25MAN_HEROIC)
|
|
nonHeroicDungeon = GetLFGDungeon(map->GetId(), RAID_DIFFICULTY_25MAN_NORMAL);
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::InitializeMap: Map {} ({}{}) | is a Heroic dungeon that is not in LFG. Using non-heroic LFG levels.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : ""
|
|
);
|
|
|
|
if (nonHeroicDungeon)
|
|
{
|
|
mapABInfo->lfgMinLevel = nonHeroicDungeon->MinLevel;
|
|
mapABInfo->lfgMaxLevel = nonHeroicDungeon->MaxLevel;
|
|
mapABInfo->lfgTargetLevel = nonHeroicDungeon->TargetLevel;
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("module.AutoBalance", "AutoBalance::InitializeMap: Map {} ({}{}) | Could not determine LFG level ranges for this map. Level will bet set to 0.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : ""
|
|
);
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG("module.AutoBalance", "AutoBalance::InitializeMap: Map {} ({}{}) | LFG levels ({}-{}) (target {}). {} for AutoBalancing.",
|
|
map->GetMapName(),
|
|
map->GetId(),
|
|
map->GetInstanceId() ? "-" + std::to_string(map->GetInstanceId()) : "",
|
|
mapABInfo->lfgMinLevel ? std::to_string(mapABInfo->lfgMinLevel) : "?",
|
|
mapABInfo->lfgMaxLevel ? std::to_string(mapABInfo->lfgMaxLevel) : "?",
|
|
mapABInfo->lfgTargetLevel ? std::to_string(mapABInfo->lfgTargetLevel) : "?",
|
|
mapABInfo->enabled ? "Enabled" : "Disabled"
|
|
);
|
|
|
|
return mapABInfo;
|
|
}
|