/** * EQEmulator: Everquest Server Emulator * Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server) * * 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; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY except by those people which sell it, which * are required to give you total support for your newly bought product; * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "npc_scale_manager.h" #include "../common/repositories/npc_scale_global_base_repository.h" #include "../common/repositories/npc_types_repository.h" /** * @param npc */ void NpcScaleManager::ScaleNPC( NPC *npc, bool always_scale, bool override_special_abilities ) { if (npc->IsSkipAutoScale() || npc->GetNPCTypeID() == 0) { return; } auto npc_type = GetNPCScalingType(npc); auto npc_level = npc->GetLevel(); auto is_auto_scaled = IsAutoScaled(npc); auto zone_id = zone->GetZoneID(); auto instance_version = zone->GetInstanceVersion(); global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel( npc_type, npc_level, zone_id, instance_version ); if (!scale_data.level) { LogNPCScaling( "NPC: [{}] - scaling data not found for type [{}] level [{}]", npc->GetCleanName(), npc_type, npc_level ); return; } if (always_scale || (npc->GetAC() == 0 && is_auto_scaled)) { npc->ModifyNPCStat("ac", std::to_string(scale_data.ac)); } if (always_scale || npc->GetMaxHP() == 0) { npc->ModifyNPCStat("max_hp", std::to_string(scale_data.hp)); npc->RestoreHealth(); } if (always_scale || npc->GetAccuracyRating() == 0) { npc->ModifyNPCStat("accuracy", std::to_string(scale_data.accuracy)); } if (always_scale || npc->GetSlowMitigation() == 0) { npc->ModifyNPCStat("slow_mitigation", std::to_string(scale_data.slow_mitigation)); } if (always_scale || npc->GetATK() == 0) { npc->ModifyNPCStat("atk", std::to_string(scale_data.attack)); } if (always_scale || npc->GetSTR() == 0) { npc->ModifyNPCStat("str", std::to_string(scale_data.strength)); } if (always_scale || npc->GetSTA() == 0) { npc->ModifyNPCStat("sta", std::to_string(scale_data.stamina)); } if (always_scale || npc->GetDEX() == 0) { npc->ModifyNPCStat("dex", std::to_string(scale_data.dexterity)); } if (always_scale || npc->GetAGI() == 0) { npc->ModifyNPCStat("agi", std::to_string(scale_data.agility)); } if (always_scale || npc->GetINT() == 0) { npc->ModifyNPCStat("int", std::to_string(scale_data.intelligence)); } if (always_scale || npc->GetWIS() == 0) { npc->ModifyNPCStat("wis", std::to_string(scale_data.wisdom)); } if (always_scale || npc->GetCHA() == 0) { npc->ModifyNPCStat("cha", std::to_string(scale_data.charisma)); } if (always_scale || npc->GetMR() == 0) { npc->ModifyNPCStat("mr", std::to_string(scale_data.magic_resist)); } if (always_scale || npc->GetCR() == 0) { npc->ModifyNPCStat("cr", std::to_string(scale_data.cold_resist)); } if (always_scale || npc->GetFR() == 0) { npc->ModifyNPCStat("fr", std::to_string(scale_data.fire_resist)); } if (always_scale || npc->GetPR() == 0) { npc->ModifyNPCStat("pr", std::to_string(scale_data.poison_resist)); } if (always_scale || npc->GetDR() == 0) { npc->ModifyNPCStat("dr", std::to_string(scale_data.disease_resist)); } if (always_scale || (npc->GetCorrup() == 0 && is_auto_scaled)) { npc->ModifyNPCStat("cor", std::to_string(scale_data.corruption_resist)); } if (always_scale || (npc->GetPhR() == 0 && is_auto_scaled)) { npc->ModifyNPCStat("phr", std::to_string(scale_data.physical_resist)); } // If either is scaled, both need to be. The values for base_damage and min_damage will be in flux until // both are complete. if (always_scale || npc->GetMinDMG() == 0 || npc->GetMaxDMG() == 0) { int64 min_dmg = scale_data.min_dmg; int64 max_dmg = scale_data.max_dmg; if (RuleB(Combat, UseNPCDamageClassLevelMods)) { uint32 class_level_damage_mod = GetClassLevelDamageMod(npc->GetLevel(), npc->GetClass()); min_dmg = (min_dmg * class_level_damage_mod) / 220; max_dmg = (max_dmg * class_level_damage_mod) / 220; } npc->ModifyNPCStat("min_hit", std::to_string(min_dmg)); npc->ModifyNPCStat("max_hit", std::to_string(max_dmg)); LogNPCScaling("ClassLevelDamageMod::min_dmg base: [{}] calc: [{}]", scale_data.min_dmg, min_dmg); LogNPCScaling("ClassLevelDamageMod::max_dmg base: [{}] calc: [{}]", scale_data.max_dmg, max_dmg); } if (always_scale || (npc->GetHPRegen() == 0 && is_auto_scaled)) { npc->ModifyNPCStat("hp_regen", std::to_string(scale_data.hp_regen_rate)); } if (always_scale || (npc->GetHPRegenPerSecond() == 0 && is_auto_scaled)) { npc->ModifyNPCStat("hp_regen_per_second", std::to_string(scale_data.hp_regen_per_second)); } if (always_scale || npc->GetAttackDelay() == 0) { npc->ModifyNPCStat("attack_delay", std::to_string(scale_data.attack_delay)); } if (always_scale || npc->GetSpellScale() == 0) { npc->ModifyNPCStat("spellscale", std::to_string(scale_data.spell_scale)); } if (always_scale || npc->GetHealScale() == 0) { npc->ModifyNPCStat("healscale", std::to_string(scale_data.heal_scale)); } if (always_scale || npc->GetAvoidanceRating() == 0) { npc->ModifyNPCStat("avoidance", std::to_string(scale_data.avoidance)); } if (always_scale || npc->GetHeroicStrikethrough() == 0) { npc->ModifyNPCStat("heroic_strikethrough", std::to_string(scale_data.heroic_strikethrough)); } if (override_special_abilities || (!npc->HasSpecialAbilities() && is_auto_scaled)) { npc->ModifyNPCStat("special_abilities", scale_data.special_abilities); } if (LogSys.log_settings[Logs::NPCScaling].is_category_enabled == 1) { std::string scale_log; for (const auto &stat : scaling_stats) { auto v = fmt::format("modify_stat_{}", stat); if (npc->EntityVariableExists(v)) { scale_log += fmt::format("{}: {} ", stat, npc->GetEntityVariable(v)); } } LogNPCScaling( "([{}]) level: [{}] type: [{}] Auto: [{}] Setting: [{}]", npc->GetCleanName(), npc_level, npc_type, (is_auto_scaled ? "true" : "false"), scale_log ); } } void NpcScaleManager::ResetNPCScaling(NPC* npc) { for (const auto &scaling_stat : scaling_stats) { auto stat_name = fmt::format("modify_stat_{}", scaling_stat); auto reset_value = std::to_string(0); if (npc->EntityVariableExists(stat_name)) { npc->ModifyNPCStat(scaling_stat, reset_value); } } } bool NpcScaleManager::LoadScaleData() { auto rows = NpcScaleGlobalBaseRepository::All(content_db); for (const auto &s : rows) { if ( s.zone_id_list.empty() || s.instance_version_list.empty() ) { continue; } global_npc_scale scale_data; scale_data.type = s.type; scale_data.level = s.level; scale_data.ac = s.ac; scale_data.hp = s.hp; scale_data.accuracy = s.accuracy; scale_data.slow_mitigation = s.slow_mitigation; scale_data.attack = s.attack; scale_data.strength = s.strength; scale_data.stamina = s.stamina; scale_data.dexterity = s.dexterity; scale_data.agility = s.agility; scale_data.intelligence = s.intelligence; scale_data.wisdom = s.wisdom; scale_data.charisma = s.charisma; scale_data.magic_resist = s.magic_resist; scale_data.cold_resist = s.cold_resist; scale_data.fire_resist = s.fire_resist; scale_data.poison_resist = s.poison_resist; scale_data.disease_resist = s.disease_resist; scale_data.corruption_resist = s.corruption_resist; scale_data.physical_resist = s.physical_resist; scale_data.min_dmg = s.min_dmg; scale_data.max_dmg = s.max_dmg; scale_data.hp_regen_rate = s.hp_regen_rate; scale_data.hp_regen_per_second = s.hp_regen_per_second; scale_data.attack_delay = s.attack_delay; scale_data.spell_scale = s.spell_scale; scale_data.heal_scale = s.heal_scale; scale_data.avoidance = s.avoidance; scale_data.heroic_strikethrough = s.heroic_strikethrough; if (!s.special_abilities.empty()) { scale_data.special_abilities = s.special_abilities; } const auto has_multiple_zones = Strings::Contains(s.zone_id_list, "|"); const auto has_multiple_versions = Strings::Contains(s.instance_version_list, "|"); if (!has_multiple_zones && !has_multiple_versions) { scale_data.zone_id = Strings::ToUnsignedInt(s.zone_id_list); scale_data.instance_version = static_cast(Strings::ToUnsignedInt(s.instance_version_list)); npc_global_base_scaling_data.emplace( std::make_pair( std::make_tuple( scale_data.type, scale_data.level, scale_data.zone_id, scale_data.instance_version ), scale_data ) ); } else if (has_multiple_zones && !has_multiple_versions) { scale_data.instance_version = static_cast(Strings::ToUnsignedInt(s.instance_version_list)); const auto zones = Strings::Split(s.zone_id_list, "|"); for (const auto &z : zones) { scale_data.zone_id = Strings::ToUnsignedInt(z); npc_global_base_scaling_data.emplace( std::make_pair( std::make_tuple( scale_data.type, scale_data.level, scale_data.zone_id, scale_data.instance_version ), scale_data ) ); } } else if (!has_multiple_zones) { scale_data.zone_id = Strings::ToUnsignedInt(s.zone_id_list); const auto versions = Strings::Split(s.instance_version_list, "|"); for (const auto &v : versions) { scale_data.instance_version = static_cast(Strings::ToUnsignedInt(v)); npc_global_base_scaling_data.emplace( std::make_pair( std::make_tuple( scale_data.type, scale_data.level, scale_data.zone_id, scale_data.instance_version ), scale_data ) ); } } else { const auto zones = Strings::Split(s.zone_id_list, "|"); const auto versions = Strings::Split(s.instance_version_list, "|"); for (const auto &z : zones) { scale_data.zone_id = Strings::ToUnsignedInt(z); for (const auto &v : versions) { scale_data.instance_version = static_cast(Strings::ToUnsignedInt(v)); npc_global_base_scaling_data.emplace( std::make_pair( std::make_tuple( scale_data.type, scale_data.level, scale_data.zone_id, scale_data.instance_version ), scale_data ) ); } } } } LogInfo("Loaded [{}] global scaling data entries", Strings::Commify(rows.size())); return true; } /** * @param npc_type * @param npc_level * @return NpcScaleManager::global_npc_scale */ NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel( int8 npc_type, uint8 npc_level, uint32 zone_id, uint16 instance_version ) { auto iter = npc_global_base_scaling_data.find( std::make_tuple( npc_type, npc_level, zone_id, instance_version ) ); if (iter != npc_global_base_scaling_data.end()) { return iter->second; } iter = npc_global_base_scaling_data.find( std::make_tuple( npc_type, npc_level, zone_id, 0 ) ); if (iter != npc_global_base_scaling_data.end()) { return iter->second; } iter = npc_global_base_scaling_data.find( std::make_tuple( npc_type, npc_level, 0, 0 ) ); if (iter != npc_global_base_scaling_data.end()) { return iter->second; } return {}; } /** * @param level * @param npc_class * @return */ uint32 NpcScaleManager::GetClassLevelDamageMod(uint32 level, uint32 npc_class) { uint32 multiplier; switch (npc_class) { case Class::Warrior: { if (level < 20) { multiplier = 220; } else if (level < 30) { multiplier = 230; } else if (level < 40) { multiplier = 250; } else if (level < 53) { multiplier = 270; } else if (level < 57) { multiplier = 280; } else if (level < 60) { multiplier = 290; } else if (level < 70) { multiplier = 300; } else { multiplier = 311; } break; } case Class::Druid: case Class::Cleric: case Class::Shaman: { if (level < 70) { multiplier = 150; } else { multiplier = 157; } break; } case Class::Berserker: case Class::Paladin: case Class::ShadowKnight: { if (level < 35) { multiplier = 210; } else if (level < 45) { multiplier = 220; } else if (level < 51) { multiplier = 230; } else if (level < 56) { multiplier = 240; } else if (level < 60) { multiplier = 250; } else if (level < 68) { multiplier = 260; } else { multiplier = 270; } break; } case Class::Monk: case Class::Bard: case Class::Rogue: case Class::Beastlord: { if (level < 51) { multiplier = 180; } else if (level < 58) { multiplier = 190; } else if (level < 70) { multiplier = 200; } else { multiplier = 210; } break; } case Class::Ranger: { if (level < 58) { multiplier = 200; } else if (level < 70) { multiplier = 210; } else { multiplier = 220; } break; } case Class::Magician: case Class::Wizard: case Class::Necromancer: case Class::Enchanter: { if (level < 70) { multiplier = 120; } else { multiplier = 127; } break; } default: { if (level < 35) { multiplier = 210; } else if (level < 45) { multiplier = 220; } else if (level < 51) { multiplier = 230; } else if (level < 56) { multiplier = 240; } else if (level < 60) { multiplier = 250; } else { multiplier = 260; } break; } } return multiplier; } /** * @param npc * @return int8 */ int8 NpcScaleManager::GetNPCScalingType(NPC *&npc) { std::string npc_name = npc->GetName(); if (npc->IsRaidTarget()) { return 2; } if (npc->IsRareSpawn() || npc_name.find('#') != std::string::npos || isupper(npc_name[0])) { return 1; } return 0; } /** * @param npc * @return std::string */ std::string NpcScaleManager::GetNPCScalingTypeName(NPC *&npc) { if (int8 scaling_type = GetNPCScalingType(npc); scaling_type == 1) { return "Named"; } if (npc->IsRaidTarget()) { return "Raid"; } return "Trash"; } /** * Determines based on minimum criteria if NPC is auto scaled for certain things to be scaled like * special abilities. We use this so we don't blindly assume we want things to be applied * * @param npc * @return */ bool NpcScaleManager::IsAutoScaled(NPC* npc) { return ( npc->GetHP() == 0 && npc->GetMaxDMG() == 0 && npc->GetMinDMG() == 0 && npc->GetSTR() == 0 && npc->GetSTA() == 0 && npc->GetDEX() == 0 && npc->GetAGI() == 0 && npc->GetINT() == 0 && npc->GetWIS() == 0 && npc->GetCHA() == 0 && npc->GetMR() == 0 && npc->GetFR() == 0 && npc->GetCR() == 0 && npc->GetPR() == 0 && npc->GetDR() == 0 ); } /** * Returns false if scaling data not found * @param npc * @return */ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc) { auto npc_type = GetNPCScalingType(npc); auto npc_level = npc->GetLevel(); auto zone_id = zone->GetZoneID(); auto instance_version = zone->GetInstanceVersion(); global_npc_scale g = GetGlobalScaleDataForTypeLevel( npc_type, npc_level, zone_id, instance_version ); if (!g.level) { LogNPCScaling( "NPC: [{}] - scaling data not found for type [{}] level [{}]", npc->GetCleanName(), npc_type, npc_level ); return false; } auto n = NpcTypesRepository::FindOne(content_db, static_cast(npc->GetNPCTypeID())); if (n.id > 0) { n.AC = g.ac; n.hp = g.hp; n.Accuracy = g.accuracy; n.slow_mitigation = g.slow_mitigation; n.ATK = g.attack; n.STR = g.strength; n.STA = g.stamina; n.DEX = g.dexterity; n.AGI = g.agility; n._INT = g.intelligence; n.WIS = g.wisdom; n.CHA = g.charisma; n.MR = g.magic_resist; n.CR = g.cold_resist; n.FR = g.fire_resist; n.PR = g.poison_resist; n.DR = g.disease_resist; n.Corrup = g.corruption_resist; n.PhR = g.physical_resist; n.mindmg = g.min_dmg; n.maxdmg = g.max_dmg; n.hp_regen_rate = g.hp_regen_rate; n.hp_regen_per_second = g.hp_regen_per_second; n.attack_delay = g.attack_delay; n.spellscale = static_cast(g.spell_scale); n.healscale = static_cast(g.heal_scale); n.Avoidance = g.avoidance; n.heroic_strikethrough = g.heroic_strikethrough; n.special_abilities = g.special_abilities; return NpcTypesRepository::UpdateOne(content_db, n); } return false; } /** * Returns false if scaling data not found * @param npc * @return */ bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc) { auto npc_type = GetNPCScalingType(npc); auto npc_level = npc->GetLevel(); auto zone_id = zone->GetZoneID(); auto instance_version = zone->GetInstanceVersion(); global_npc_scale d = GetGlobalScaleDataForTypeLevel( npc_type, npc_level, zone_id, instance_version ); if (!d.level) { LogNPCScaling( "NPC: [{}] - scaling data not found for type [{}] level [{}]", npc->GetCleanName(), npc_type, npc_level ); return false; } auto n = NpcTypesRepository::FindOne(content_db, static_cast(npc->GetNPCTypeID())); if (n.id > 0) { n.AC = 0; n.hp = 0; n.Accuracy = 0; n.slow_mitigation = 0; n.ATK = 0; n.STR = 0; n.STA = 0; n.DEX = 0; n.AGI = 0; n._INT = 0; n.WIS = 0; n.CHA = 0; n.MR = 0; n.CR = 0; n.FR = 0; n.PR = 0; n.DR = 0; n.Corrup = 0; n.PhR = 0; n.mindmg = 0; n.maxdmg = 0; n.hp_regen_rate = 0; n.hp_regen_per_second = 0; n.attack_delay = 0; n.spellscale = 0; n.healscale = 0; n.Avoidance = 0; n.heroic_strikethrough = 0; n.special_abilities = ""; return NpcTypesRepository::UpdateOne(content_db, n); } return false; }