/* * Copyright (C) 2012 CVMagic * Copyright (C) 2008-2010 TrinityCore * Copyright (C) 2006-2009 ScriptDev2 * Copyright (C) 1985-2010 {VAS} KalCorp * * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * Script Name: AutoBalance * Original Authors: KalCorp and Vaughner * Maintainer(s): CVMagic * Original Script Name: VAS.AutoBalance * Description: This script is intended to scale based on number of players, instance mobs & world bosses' health, mana, and damage. * * Touched up by SPP MDic for Trinitycore Custom Changes Branch */ #include "Configuration/Config.h" #include "Unit.h" #include "Chat.h" #include "Creature.h" #include "Player.h" #include "ObjectMgr.h" #include "MapManager.h" #include "World.h" #include "Map.h" #include "ScriptMgr.h" #include "Language.h" #include #include "Log.h" #include "Group.h" #include "DataMap.h" #include "DBCStores.h" class AutoBalanceCreatureInfo : public DataMap::Base { public: AutoBalanceCreatureInfo() {} AutoBalanceCreatureInfo(uint32 count, float dmg, float hpRate, float manaRate, float armorRate, uint8 selLevel) : instancePlayerCount(count), selectedLevel(selLevel), DamageMultiplier(dmg), HealthMultiplier(hpRate), ManaMultiplier(manaRate), ArmorMultiplier(armorRate) {} uint32 instancePlayerCount = 0; uint8 selectedLevel = 0; // this is used to detect creatures that update their entry uint32 entry = 0; float DamageMultiplier = 1; float HealthMultiplier = 1; float ManaMultiplier = 1; float ArmorMultiplier = 1; }; class AutoBalanceMapInfo : public DataMap::Base { public: AutoBalanceMapInfo() {} AutoBalanceMapInfo(uint32 count, uint8 selLevel) : playerCount(count), mapLevel(selLevel) {} uint32 playerCount = 0; uint8 mapLevel = 0; }; // The map values correspond with the .AutoBalance.XX.Name entries in the configuration file. static std::map forcedCreatureIds; static int8 PlayerCountDifficultyOffset, LevelScaling, higherOffset, lowerOffset; static bool enabled, LevelEndGameBoost, DungeonsOnly, PlayerChangeNotify, LevelUseDb, DungeonScaleDownXP; static float globalRate, healthMultiplier, manaMultiplier, armorMultiplier, damageMultiplier, MinHPModifier, MinManaModifier, MinDamageModifier, InflectionPoint, InflectionPointRaid, InflectionPointRaid10M, InflectionPointRaid25M, InflectionPointRaid30M, InflectionPointHeroic, InflectionPointRaidHeroic, InflectionPointRaid10MHeroic, InflectionPointRaid25MHeroic, InflectionPointRaid30MHeroic, BossInflectionMult; int GetValidDebugLevel() { int debugLevel = sConfigMgr->GetIntDefault("AutoBalance.DebugLevel", 2); if ((debugLevel < 0) || (debugLevel > 3)) { return 1; } return debugLevel; } // 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); // Process each Creature ID in the string, delimited by the comma - "," while (std::getline(creatureIdsStream, delimitedValue, ',')) { int creatureId = atoi(delimitedValue.c_str()); if (creatureId >= 0) { forcedCreatureIds[creatureId] = forcedPlayerCount; } } } int GetForcedNumPlayers(int creatureId) { // Don't want the forcedCreatureIds map to blowup to a massive empty array if (forcedCreatureIds.find(creatureId) == forcedCreatureIds.end()) { return -1; } return forcedCreatureIds[creatureId]; } void getAreaLevel(Map* map, uint8 areaid, uint8& min, uint8& max) { LFGDungeonEntry const* dungeon = GetLFGDungeon(map->GetId(), map->GetDifficulty()); if (dungeon && (map->IsDungeon() || map->IsRaid())) { min = dungeon->MinLevel; max = dungeon->TargetLevel ? dungeon->TargetLevel : dungeon->MaxLevel; } if (!min && !max) { AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaid); if (areaEntry && areaEntry->ExplorationLevel > 0) { min = areaEntry->ExplorationLevel; max = areaEntry->ExplorationLevel; } } } class AutoBalance_WorldScript : public WorldScript { public: AutoBalance_WorldScript() : WorldScript("AutoBalance_WorldScript") {} void OnConfigLoad(bool /*reload*/) override { SetInitialWorldSettings(); } void OnStartup() override { } void SetInitialWorldSettings() override { forcedCreatureIds.clear(); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.ForcedID40", ""), 40); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.ForcedID25", ""), 25); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.ForcedID10", ""), 10); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.ForcedID5", ""), 5); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.ForcedID2", ""), 2); LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("AutoBalance.DisabledID", ""), 0); enabled = sConfigMgr->GetBoolDefault("AutoBalance.enable", 1); LevelEndGameBoost = sConfigMgr->GetBoolDefault("AutoBalance.LevelEndGameBoost", 1); DungeonsOnly = sConfigMgr->GetBoolDefault("AutoBalance.DungeonsOnly", 1); PlayerChangeNotify = sConfigMgr->GetBoolDefault("AutoBalance.PlayerChangeNotify", 1); LevelUseDb = sConfigMgr->GetBoolDefault("AutoBalance.levelUseDbValuesWhenExists", 1); DungeonScaleDownXP = sConfigMgr->GetBoolDefault("AutoBalance.DungeonScaleDownXP", 0); LevelScaling = sConfigMgr->GetIntDefault("AutoBalance.levelScaling", 1); PlayerCountDifficultyOffset = sConfigMgr->GetIntDefault("AutoBalance.playerCountDifficultyOffset", 0); higherOffset = sConfigMgr->GetIntDefault("AutoBalance.levelHigherOffset", 3); lowerOffset = sConfigMgr->GetIntDefault("AutoBalance.levelLowerOffset", 0); InflectionPoint = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPoint", 0.5f); InflectionPointRaid = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid", InflectionPoint); InflectionPointRaid30M = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid30M", InflectionPointRaid); InflectionPointRaid25M = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid25M", InflectionPointRaid); InflectionPointRaid10M = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid10M", InflectionPointRaid); InflectionPointHeroic = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointHeroic", InflectionPoint); InflectionPointRaidHeroic = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaidHeroic", InflectionPointRaid); InflectionPointRaid30MHeroic = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid30MHeroic", InflectionPointRaid30M); InflectionPointRaid25MHeroic = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid25MHeroic", InflectionPointRaid25M); InflectionPointRaid10MHeroic = sConfigMgr->GetFloatDefault("AutoBalance.InflectionPointRaid10MHeroic", InflectionPointRaid10M); BossInflectionMult = sConfigMgr->GetFloatDefault("AutoBalance.BossInflectionMult", 1.0f); globalRate = sConfigMgr->GetFloatDefault("AutoBalance.rate.global", 1.0f); healthMultiplier = sConfigMgr->GetFloatDefault("AutoBalance.rate.health", 1.0f); manaMultiplier = sConfigMgr->GetFloatDefault("AutoBalance.rate.mana", 1.0f); armorMultiplier = sConfigMgr->GetFloatDefault("AutoBalance.rate.armor", 1.0f); damageMultiplier = sConfigMgr->GetFloatDefault("AutoBalance.rate.damage", 1.0f); MinHPModifier = sConfigMgr->GetFloatDefault("AutoBalance.MinHPModifier", 0.1f); MinManaModifier = sConfigMgr->GetFloatDefault("AutoBalance.MinManaModifier", 0.1f); MinDamageModifier = sConfigMgr->GetFloatDefault("AutoBalance.MinDamageModifier", 0.1f); } }; class AutoBalance_PlayerScript : public PlayerScript { public: AutoBalance_PlayerScript() : PlayerScript("AutoBalance_PlayerScript") { } void OnLogin(Player* Player, bool /*firstLogin*/) override { if (sConfigMgr->GetBoolDefault("AutoBalanceAnnounce.enable", true)) { ChatHandler(Player->GetSession()).SendSysMessage("This server is running the |cff4CFF00AutoBalance |rmodule."); } } virtual void OnLevelChanged(Player* player, uint8 /*oldlevel*/) override { if (!enabled || !player) { return; } if (LevelScaling == 0) { return; } AutoBalanceMapInfo* mapABInfo = player->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); if (mapABInfo->mapLevel < player->GetLevel()) { mapABInfo->mapLevel = player->GetLevel(); } } void OnGiveXP(Player* player, uint32& amount, Unit* victim) override { if (victim && DungeonScaleDownXP) { Map* map = player->GetMap(); if (map->IsDungeon()) { // Ensure that the players always get the same XP, even when entering the dungeon alone uint32 maxPlayerCount = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId()))->GetMaxPlayers(); uint32 currentPlayerCount = map->GetPlayersCountExceptGMs(); amount *= (float)currentPlayerCount / maxPlayerCount; } } } }; class AutoBalance_UnitScript : public UnitScript { public: AutoBalance_UnitScript() : UnitScript("AutoBalance_UnitScript") { } // uint32 DealDamage(Unit *AttackerUnit, Unit *playerVictim, uint32 damage, DamageEffectType /*damagetype*/) { // return _Modifer_DealDamage(playerVictim, AttackerUnit, damage); // } void ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage) override { damage = _Modifer_DealDamage(target, attacker, damage); } void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage) override { damage = _Modifer_DealDamage(target, attacker, damage); } void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage) override { damage = _Modifer_DealDamage(target, attacker, damage); } void ModifyHealRecieved(Unit* target, Unit* attacker, uint32& damage) override { damage = _Modifer_DealDamage(target, attacker, damage); } uint32 _Modifer_DealDamage(Unit* target, Unit* attacker, uint32 damage) { if (!enabled) { return damage; } if (!attacker || attacker->GetTypeId() == TYPEID_PLAYER || !attacker->IsInWorld()) { return damage; } float damageMultiplier = attacker->CustomData.GetDefault("AutoBalanceCreatureInfo")->DamageMultiplier; if (damageMultiplier == 1) { return damage; } if (!(!DungeonsOnly || (target->GetMap()->IsDungeon() && attacker->GetMap()->IsDungeon()) || (attacker->GetMap()->IsBattleground() && target->GetMap()->IsBattleground()))) { return damage; } if ((attacker->IsHunterPet() || attacker->IsPet() || attacker->IsSummon()) && attacker->IsControlledByPlayer()) { return damage; } return damage * damageMultiplier; } }; class AutoBalance_AllMapScript : public AllMapScript { public: AutoBalance_AllMapScript() : AllMapScript("AutoBalance_AllMapScript") { } void OnPlayerEnterAll(Map* map, Player* player) { if (!enabled) { return; } if (player->IsGameMaster()) { return; } AutoBalanceMapInfo* mapABInfo = map->CustomData.GetDefault("AutoBalanceMapInfo"); // always check level, even if not conf enabled // because we can enable at runtime and we need this information if (player) { if (player->GetLevel() > mapABInfo->mapLevel) { mapABInfo->mapLevel = player->GetLevel(); } } else { Map::PlayerList const& playerList = map->GetPlayers(); if (!playerList.isEmpty()) { for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) { if (Player* playerHandle = playerIteration->GetSource()) { if (!playerHandle->IsGameMaster() && playerHandle->GetLevel() > mapABInfo->mapLevel) { mapABInfo->mapLevel = playerHandle->GetLevel(); } } } } } mapABInfo->playerCount++; //(maybe we've to found a safe solution to avoid player recount each time) //mapABInfo->playerCount = map->GetPlayersCountExceptGMs(); if (PlayerChangeNotify) { if (map->GetEntry()->IsDungeon() && player) { Map::PlayerList const& playerList = map->GetPlayers(); if (!playerList.isEmpty()) { for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) { if (Player* playerHandle = playerIteration->GetSource()) { ChatHandler chatHandle = ChatHandler(playerHandle->GetSession()); chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s entered the Instance %s. Auto setting player count to %u (Player Difficulty Offset = %u) |r", player->GetName().c_str(), map->GetMapName(), mapABInfo->playerCount + PlayerCountDifficultyOffset, PlayerCountDifficultyOffset); } } } } } } void OnPlayerLeaveAll(Map* map, Player* player) { if (!enabled) { return; } if (player->IsGameMaster()) { return; } AutoBalanceMapInfo* mapABInfo = map->CustomData.GetDefault("AutoBalanceMapInfo"); // (maybe we've to found a safe solution to avoid player recount each time) mapABInfo->playerCount--; // mapABInfo->playerCount = map->GetPlayersCountExceptGMs(); // always check level, even if not conf enabled // because we can enable at runtime and we need this information if (!mapABInfo->playerCount) { mapABInfo->mapLevel = 0; return; } if (PlayerChangeNotify) { if (map->GetEntry()->IsDungeon() && player) { Map::PlayerList const& playerList = map->GetPlayers(); if (!playerList.isEmpty()) { for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) { if (Player* playerHandle = playerIteration->GetSource()) { ChatHandler chatHandle = ChatHandler(playerHandle->GetSession()); chatHandle.PSendSysMessage("|cffFF0000 [-AutoBalance]|r|cffFF8000 %s left the Instance %s. Auto setting player count to %u (Player Difficulty Offset = %u) |r", player->GetName().c_str(), map->GetMapName(), mapABInfo->playerCount, PlayerCountDifficultyOffset); } } } } } } }; class AutoBalance_AllCreatureScript : public AllCreatureScript { public: AutoBalance_AllCreatureScript() : AllCreatureScript("AutoBalance_AllCreatureScript") { } void Creature_SelectLevel(const CreatureTemplate* /*creatureTemplate*/, Creature* creature) override { if (!enabled) { return; } ModifyCreatureAttributes(creature, true); } void OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) override { if (!enabled) { return; } ModifyCreatureAttributes(creature); } bool checkLevelOffset(uint8 selectedLevel, uint8 targetLevel) { return selectedLevel && ((targetLevel >= selectedLevel && targetLevel <= (selectedLevel + higherOffset)) || (targetLevel <= selectedLevel && targetLevel >= (selectedLevel - lowerOffset))); } void ModifyCreatureAttributes(Creature* creature, bool resetSelLevel = false) { if (!creature || !creature->GetMap()) { return; } if (!creature->GetMap()->IsDungeon() && !creature->GetMap()->IsBattleground() && DungeonsOnly) { return; } if (((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer())) { return; } AutoBalanceMapInfo* mapABInfo = creature->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); if (!mapABInfo->mapLevel) { return; } CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate(); InstanceMap* instanceMap = ((InstanceMap*)sMapMgr->FindMap(creature->GetMapId(), creature->GetInstanceId())); uint32 maxNumberOfPlayers = instanceMap->GetMaxPlayers(); int forcedNumPlayers = GetForcedNumPlayers(creatureTemplate->Entry); if (forcedNumPlayers > 0) { // Force maxNumberOfPlayers to be changed to match the Configuration entries ForcedID2, ForcedID5, ForcedID10, ForcedID20, ForcedID25, ForcedID40 maxNumberOfPlayers = forcedNumPlayers; } else if (forcedNumPlayers == 0) { // forcedNumPlayers 0 means that the creature is contained in DisabledID -> no scaling return; } AutoBalanceCreatureInfo* creatureABInfo = creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); // force resetting selected level. // this is also a "workaround" to fix bug of not recalculated // attributes when UpdateEntry has been used. // TODO: It's better and faster to implement a core hook // in that position and force a recalculation then if ((creatureABInfo->entry != 0 && creatureABInfo->entry != creature->GetEntry()) || resetSelLevel) { // force a recalculation creatureABInfo->selectedLevel = 0; } if (!creature->IsAlive()) { return; } uint32 curCount = mapABInfo->playerCount + PlayerCountDifficultyOffset; uint8 bonusLevel = creatureTemplate->rank == CREATURE_ELITE_WORLDBOSS ? 3 : 0; // already scaled if (creatureABInfo->selectedLevel > 0) { if (LevelScaling) { if (checkLevelOffset(mapABInfo->mapLevel + bonusLevel, creature->GetLevel()) && checkLevelOffset(creatureABInfo->selectedLevel, creature->GetLevel()) && creatureABInfo->instancePlayerCount == curCount) { return; } } else if (creatureABInfo->instancePlayerCount == curCount) { return; } } creatureABInfo->instancePlayerCount = curCount; // no players in map, do not modify attributes if (!creatureABInfo->instancePlayerCount) { return; } uint8 originalLevel = creatureTemplate->maxlevel; uint8 level = mapABInfo->mapLevel; uint8 areaMinLvl, areaMaxLvl; getAreaLevel(creature->GetMap(), creature->GetAreaId(), areaMinLvl, areaMaxLvl); // avoid level changing for critters and special creatures (spell summons etc.) in instances bool skipLevel = false; if (originalLevel <= 1 && areaMinLvl >= 5) { skipLevel = true; } if (LevelScaling && creature->GetMap()->IsDungeon() && !skipLevel && !checkLevelOffset(level, originalLevel)) { // change level only whithin the offsets and when in dungeon/raid if (level != creatureABInfo->selectedLevel || creatureABInfo->selectedLevel != creature->GetLevel()) { // keep bosses +3 level creatureABInfo->selectedLevel = level + bonusLevel; creature->SetLevel(creatureABInfo->selectedLevel); } } else { creatureABInfo->selectedLevel = creature->GetLevel(); } creatureABInfo->entry = creature->GetEntry(); bool useDefStats = false; if (LevelUseDb && creature->GetLevel() >= creatureTemplate->minlevel && creature->GetLevel() <= creatureTemplate->maxlevel) { useDefStats = true; } CreatureBaseStats const* origCreatureStats = sObjectMgr->GetCreatureBaseStats(originalLevel, creatureTemplate->unit_class); CreatureBaseStats const* creatureStats = sObjectMgr->GetCreatureBaseStats(creatureABInfo->selectedLevel, creatureTemplate->unit_class); uint32 baseHealth = origCreatureStats->GenerateHealth(creatureTemplate); uint32 baseMana = origCreatureStats->GenerateMana(creatureTemplate); uint32 scaledHealth = 0; uint32 scaledMana = 0; // Note: InflectionPoint handle the number of players required to get 50% health. // you'd adjust this to raise or lower the hp modifier for per additional player in a non-whole group. // // diff modify the rate of percentage increase between // number of players. Generally the closer to the value of 1 you have this // the less gradual the rate will be. For example in a 5 man it would take 3 // total players to face a mob at full health. // // The +1 and /2 values raise the TanH function to a positive range and make // sure the modifier never goes above the value or 1.0 or below 0. // float defaultMultiplier = 1.0f; if (creatureABInfo->instancePlayerCount < maxNumberOfPlayers) { float inflectionValue = (float)maxNumberOfPlayers; if (instanceMap->IsHeroic()) { if (instanceMap->IsRaid()) { switch (instanceMap->GetMaxPlayers()) { case 10: inflectionValue *= InflectionPointRaid10MHeroic; break; case 25: inflectionValue *= InflectionPointRaid25MHeroic; break; case 30: inflectionValue *= InflectionPointRaid30MHeroic; break; default: inflectionValue *= InflectionPointRaidHeroic; } } else { inflectionValue *= InflectionPointHeroic; } } else { if (instanceMap->IsRaid()) { switch (instanceMap->GetMaxPlayers()) { case 10: inflectionValue *= InflectionPointRaid10M; break; case 25: inflectionValue *= InflectionPointRaid25M; break; case 30: inflectionValue *= InflectionPointRaid30M; break; default: inflectionValue *= InflectionPointRaid; } } else { inflectionValue *= InflectionPoint; } } if (creature->IsDungeonBoss()) { inflectionValue *= BossInflectionMult; } float diff = ((float)maxNumberOfPlayers / 5) * 1.5f; defaultMultiplier = (tanh(((float)creatureABInfo->instancePlayerCount - inflectionValue) / diff) + 1.0f) / 2.0f; } creatureABInfo->HealthMultiplier = healthMultiplier * defaultMultiplier * globalRate; if (creatureABInfo->HealthMultiplier <= MinHPModifier) { creatureABInfo->HealthMultiplier = MinHPModifier; } float hpStatsRate = 1.0f; if (!useDefStats && LevelScaling && !skipLevel) { float newBaseHealth = 0; if (level <= 60) { newBaseHealth = creatureStats->BaseHealth[0]; } else if (level <= 70) { newBaseHealth = creatureStats->BaseHealth[1]; } else { newBaseHealth = creatureStats->BaseHealth[2]; // special increasing for end-game contents if (LevelEndGameBoost) { newBaseHealth *= creatureABInfo->selectedLevel >= 75 && originalLevel < 75 ? float(creatureABInfo->selectedLevel - 70) * 0.3f : 1; } } float newHealth = newBaseHealth * creatureTemplate->ModHealth; // allows health to be different with creatures that originally // differentiate their health by different level instead of multiplier field. // expecially in dungeons. The health reduction decrease if original level is similar to the area max level if (originalLevel >= areaMinLvl && originalLevel < areaMaxLvl) { // never more than 30% float reduction = newHealth / float(areaMaxLvl - areaMinLvl) * (float(areaMaxLvl - originalLevel) * 0.3f); if (reduction > 0 && reduction < newHealth) { newHealth -= reduction; } } hpStatsRate = newHealth / float(baseHealth); } creatureABInfo->HealthMultiplier *= hpStatsRate; scaledHealth = round(((float)baseHealth * creatureABInfo->HealthMultiplier) + 1.0f); // Getting the list of Classes in this group // This will be used later on to determine what additional scaling will be required based on the ratio of tank/dps/healer // Update playerClassList with the list of all the participating Classes // GetPlayerClassList(creature, playerClassList); float manaStatsRate = 1.0f; if (!useDefStats && LevelScaling && !skipLevel) { float newMana = creatureStats->GenerateMana(creatureTemplate); manaStatsRate = newMana / float(baseMana); } creatureABInfo->ManaMultiplier = manaStatsRate * manaMultiplier * defaultMultiplier * globalRate; if (creatureABInfo->ManaMultiplier <= MinManaModifier) { creatureABInfo->ManaMultiplier = MinManaModifier; } scaledMana = round(baseMana * creatureABInfo->ManaMultiplier); float damageMul = defaultMultiplier * globalRate * damageMultiplier; // Can not be less then Min_D_Mod if (damageMul <= MinDamageModifier) { damageMul = MinDamageModifier; } if (!useDefStats && LevelScaling && !skipLevel) { float origDmgBase = origCreatureStats->GenerateBaseDamage(creatureTemplate); float newDmgBase = 0; if (level <= 60) { newDmgBase = creatureStats->BaseDamage[0]; } else if (level <= 70) { newDmgBase = creatureStats->BaseDamage[1]; } else { newDmgBase = creatureStats->BaseDamage[2]; // special increasing for end-game contents if (LevelEndGameBoost && !creature->GetMap()->IsRaid()) newDmgBase *= creatureABInfo->selectedLevel >= 75 && originalLevel < 75 ? float(creatureABInfo->selectedLevel - 70) * 0.3f : 1; } damageMul *= newDmgBase / origDmgBase; } creatureABInfo->ArmorMultiplier = defaultMultiplier * globalRate * armorMultiplier; uint32 newBaseArmor = round(creatureABInfo->ArmorMultiplier * ((useDefStats || !LevelScaling || skipLevel) ? origCreatureStats->GenerateArmor(creatureTemplate) : creatureStats->GenerateArmor(creatureTemplate))); uint32 prevMaxHealth = creature->GetMaxHealth(); uint32 prevMaxPower = creature->GetMaxPower(POWER_MANA); uint32 prevHealth = creature->GetHealth(); uint32 prevPower = creature->GetPower(POWER_MANA); Powers pType = creature->GetPowerType(); creature->SetArmor(newBaseArmor); creature->SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, (float)newBaseArmor); creature->SetCreateHealth(scaledHealth); creature->SetMaxHealth(scaledHealth); creature->ResetPlayerDamageReq(); creature->SetCreateMana(scaledMana); creature->SetMaxPower(POWER_MANA, scaledMana); creature->SetStatFlatModifier(UNIT_MOD_ENERGY, BASE_VALUE, (float)100.0f); creature->SetStatFlatModifier(UNIT_MOD_RAGE, BASE_VALUE, (float)100.0f); creature->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)scaledHealth); creature->SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, (float)scaledMana); creatureABInfo->DamageMultiplier = damageMul; uint32 scaledCurHealth = prevHealth && prevMaxHealth ? float(scaledHealth) / float(prevMaxHealth) * float(prevHealth) : 0; uint32 scaledCurPower = prevPower && prevMaxPower ? float(scaledMana) / float(prevMaxPower) * float(prevPower) : 0; creature->SetHealth(scaledCurHealth); if (pType == POWER_MANA) { creature->SetPower(POWER_MANA, scaledCurPower); } else { // fix creatures with different power types creature->SetPowerType(pType); } creature->UpdateAllStats(); } }; using namespace Trinity::ChatCommands; class AutoBalance_CommandScript : public CommandScript { public: AutoBalance_CommandScript() : CommandScript("AutoBalance_CommandScript") { } ChatCommandTable GetCommands() const override { static ChatCommandTable ABCommandTable = { { "setoffset", HandleABSetOffsetCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, { "getoffset", HandleABGetOffsetCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, { "checkmap", HandleABCheckMapCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, { "mapstat", HandleABMapStatsCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, { "crstat", HandleABCreatureStatsCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, }; static ChatCommandTable commandTable = { { "vas", ABCommandTable }, }; return commandTable; } static bool HandleABSetOffsetCommand(ChatHandler* handler, uint32 offseti) { handler->PSendSysMessage("Changing Player Difficulty Offset to %i.", offseti); PlayerCountDifficultyOffset = offseti; return true; } static bool HandleABGetOffsetCommand(ChatHandler* handler) { handler->PSendSysMessage("Current Player Difficulty Offset = %i", PlayerCountDifficultyOffset); return true; } static bool HandleABCheckMapCommand(ChatHandler* handler) { Player* pl = handler->getSelectedPlayer(); if (!pl) { handler->SendSysMessage(LANG_SELECT_PLAYER_OR_PET); handler->SetSentErrorMessage(true); return false; } AutoBalanceMapInfo* mapABInfo = pl->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); mapABInfo->playerCount = pl->GetMap()->GetPlayersCountExceptGMs(); Map::PlayerList const& playerList = pl->GetMap()->GetPlayers(); uint8 level = 0; if (!playerList.isEmpty()) { for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) { if (Player* playerHandle = playerIteration->GetSource()) { if (playerHandle->GetLevel() > level) { mapABInfo->mapLevel = level = playerHandle->GetLevel(); } } } } HandleABMapStatsCommand(handler); return true; } static bool HandleABMapStatsCommand(ChatHandler* handler) { Player* pl = handler->getSelectedPlayer(); if (!pl) { handler->SendSysMessage(LANG_SELECT_PLAYER_OR_PET); handler->SetSentErrorMessage(true); return false; } AutoBalanceMapInfo* mapABInfo = pl->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); handler->PSendSysMessage("Players on map: %u", mapABInfo->playerCount); handler->PSendSysMessage("Max level of players in this map: %u", mapABInfo->mapLevel); return true; } static bool HandleABCreatureStatsCommand(ChatHandler* handler) { Creature* target = handler->getSelectedCreature(); if (!target) { handler->SendSysMessage(LANG_SELECT_CREATURE); handler->SetSentErrorMessage(true); return false; } AutoBalanceCreatureInfo* creatureABInfo = target->CustomData.GetDefault("AutoBalanceCreatureInfo"); handler->PSendSysMessage("Instance player Count: %u", creatureABInfo->instancePlayerCount); handler->PSendSysMessage("Selected level: %u", creatureABInfo->selectedLevel); handler->PSendSysMessage("Damage multiplier: %.6f", creatureABInfo->DamageMultiplier); handler->PSendSysMessage("Health multiplier: %.6f", creatureABInfo->HealthMultiplier); handler->PSendSysMessage("Mana multiplier: %.6f", creatureABInfo->ManaMultiplier); handler->PSendSysMessage("Armor multiplier: %.6f", creatureABInfo->ArmorMultiplier); return true; } }; void AddSC_AutoBalance() { new AutoBalance_WorldScript; new AutoBalance_PlayerScript; new AutoBalance_UnitScript; new AutoBalance_AllCreatureScript; new AutoBalance_AllMapScript; new AutoBalance_CommandScript; }