diff --git a/zone/command.cpp b/zone/command.cpp index 40f9d7a91..a0f8061ce 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -67,6 +67,7 @@ #include "water_map.h" #include "worldserver.h" #include "fastmath.h" +#include "npc_scale_manager.h" extern QueryServ* QServ; extern WorldServer worldserver; @@ -335,6 +336,7 @@ int command_init(void) command_add("revoke", "[charname] [1/0] - Makes charname unable to talk on OOC", 200, command_revoke) || command_add("rules", "(subcommand) - Manage server rules", 250, command_rules) || command_add("save", "- Force your player or player corpse target to be saved to the database", 50, command_save) || + command_add("scale", "- Handles npc scaling", 150, command_scale) || command_add("scribespell", "[spellid] - Scribe specified spell in your target's spell book.", 180, command_scribespell) || command_add("scribespells", "[max level] [min level] - Scribe all spells for you or your player target that are usable by them, up to level specified. (may freeze client for a few seconds)", 150, command_scribespells) || command_add("sendzonespawns", "- Refresh spawn list for all clients in zone", 150, command_sendzonespawns) || @@ -1380,7 +1382,7 @@ void command_list(Client *c, const Seperator *sep) entity_count++; - std::string entity_name = entity->GetCleanName(); + std::string entity_name = entity->GetName(); /** * Filter by name @@ -1421,7 +1423,7 @@ void command_list(Client *c, const Seperator *sep) entity_count++; - std::string entity_name = entity->GetCleanName(); + std::string entity_name = entity->GetName(); /** * Filter by name @@ -1462,7 +1464,7 @@ void command_list(Client *c, const Seperator *sep) entity_count++; - std::string entity_name = entity->GetCleanName(); + std::string entity_name = entity->GetName(); /** * Filter by name @@ -11400,6 +11402,133 @@ void command_reloadtraps(Client *c, const Seperator *sep) c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName()); } +void command_scale(Client *c, const Seperator *sep) +{ + if (sep->argnum == 0) { + c->Message(15, "# Usage # "); + c->Message(15, "#scale [static/dynamic] (With targeted NPC)"); + c->Message(15, "#scale [npc_name_search] [static/dynamic] (To make zone-wide changes)"); + c->Message(15, "#scale all [static/dynamic]"); + return; + } + + /** + * Targeted changes + */ + if (c->GetTarget() && c->GetTarget()->IsNPC() && sep->argnum < 2) { + NPC * npc = c->GetTarget()->CastToNPC(); + + bool apply_status = false; + if (strcasecmp(sep->arg[1], "dynamic") == 0) { + c->Message(15, "Applying global base scaling to npc dynamically (All stats set to zeroes)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(npc); + } + else if (strcasecmp(sep->arg[1], "static") == 0) { + c->Message(15, "Applying global base scaling to npc statically (Copying base stats onto NPC)..."); + apply_status = npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(npc); + } + else { + return; + } + + if (apply_status) { + c->Message(15, "Applied to NPC '%s' successfully!", npc->GetName()); + } + else { + c->Message(15, "Failed to load scaling data from the database " + "for this npc / type, see 'NPCScaling' log for more info"); + } + } + else if (c->GetTarget() && sep->argnum < 2) { + c->Message(15, "Target must be an npc!"); + } + + /** + * Zonewide + */ + if (sep->argnum > 1) { + + std::string scale_type; + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + scale_type = "dynamic"; + } + else if (strcasecmp(sep->arg[2], "static") == 0) { + scale_type = "static"; + } + + if (scale_type.length() <= 0) { + c->Message(15, "You must first set if you intend on using static versus dynamic for these changes"); + c->Message(15, "#scale [npc_name_search] [static/dynamic]"); + c->Message(15, "#scale all [static/dynamic]"); + return; + } + + std::string search_string = sep->arg[1]; + + auto &entity_list_search = entity_list.GetNPCList(); + + int found_count = 0; + for (auto &itr : entity_list_search) { + NPC *entity = itr.second; + + std::string entity_name = entity->GetName(); + + /** + * Filter by name + */ + if (search_string.length() > 0 && entity_name.find(search_string) == std::string::npos && strcasecmp(sep->arg[1], "all") != 0) { + continue; + } + + std::string status = "(Searching)"; + + if (strcasecmp(sep->arg[3], "apply") == 0) { + status = "(Applying)"; + + if (strcasecmp(sep->arg[2], "dynamic") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCDynamically(entity); + } + if (strcasecmp(sep->arg[2], "static") == 0) { + npc_scale_manager->ApplyGlobalBaseScalingToNPCStatically(entity); + } + } + + c->Message( + 15, + "| ID %5d | %s | x %.0f | y %0.f | z %.0f | DBID %u %s", + entity->GetID(), + entity->GetName(), + entity->GetX(), + entity->GetY(), + entity->GetZ(), + entity->GetNPCTypeID(), + status.c_str() + ); + + found_count++; + } + + if (strcasecmp(sep->arg[3], "apply") == 0) { + c->Message(15, "%s scaling applied against (%i) NPC's", sep->arg[2], found_count); + } + else { + + std::string saylink = StringFormat( + "#scale %s %s apply", + sep->arg[1], + sep->arg[2] + ); + + c->Message(15, "Found (%i) NPC's that match this search...", found_count); + c->Message( + 15, "To apply these changes, click <%s> or type %s", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), + saylink.c_str() + ); + } + } +} + // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. #ifdef BOTS #include "bot_command.h" diff --git a/zone/command.h b/zone/command.h index 16a7177c2..735cf1f70 100644 --- a/zone/command.h +++ b/zone/command.h @@ -242,6 +242,7 @@ void command_resetaa_timer(Client *c, const Seperator *sep); void command_revoke(Client *c, const Seperator *sep); void command_rules(Client *c, const Seperator *sep); void command_save(Client *c, const Seperator *sep); +void command_scale(Client *c, const Seperator *sep); void command_scribespell(Client *c, const Seperator *sep); void command_scribespells(Client *c, const Seperator *sep); void command_sendop(Client *c, const Seperator *sep); diff --git a/zone/mob.cpp b/zone/mob.cpp index 0976b3783..7c486f451 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3247,8 +3247,7 @@ void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str) const char *Mob::GetCleanName() { - if(!strlen(clean_name)) - { + if (!strlen(clean_name)) { CleanMobName(GetName(), clean_name); } @@ -3650,11 +3649,10 @@ void Mob::SetEntityVariable(const char *id, const char *m_var) m_EntityVariables[id] = n_m_var; } -const char* Mob::GetEntityVariable(const char *id) +const char *Mob::GetEntityVariable(const char *id) { auto iter = m_EntityVariables.find(id); - if(iter != m_EntityVariables.end()) - { + if (iter != m_EntityVariables.end()) { return iter->second.c_str(); } return nullptr; diff --git a/zone/npc.cpp b/zone/npc.cpp index 617bbc671..462feff4f 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -367,7 +367,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi ignore_despawn = npc_type_data->ignore_despawn; m_targetable = !npc_type_data->untargetable; - npc_scale_manager->ScaleMob(this); + npc_scale_manager->ScaleNPC(this); AISpellVar.fail_recast = static_cast(RuleI(Spells, AI_SpellCastFinishedFailRecast)); AISpellVar.engaged_no_sp_recast_min = static_cast(RuleI(Spells, AI_EngagedNoSpellMinRecast)); diff --git a/zone/npc_scale_manager.cpp b/zone/npc_scale_manager.cpp index d9f1c2d7a..6b04ac4d1 100644 --- a/zone/npc_scale_manager.cpp +++ b/zone/npc_scale_manager.cpp @@ -24,34 +24,20 @@ /** * @param mob */ -void NpcScaleManager::ScaleMob(Mob *mob) +void NpcScaleManager::ScaleNPC(NPC * npc) { - Log(Logs::General, Logs::NPCScaling, "Attempting scale on %s", mob->GetCleanName()); + Log(Logs::General, Logs::NPCScaling, "Attempting scale on %s", npc->GetCleanName()); - if (mob->IsClient()) { - return; - } + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); - NPC *npc = mob->CastToNPC(); - - int8 mob_type = 0; - int mob_level = npc->GetLevel(); - - if (npc->IsRareSpawn()) { - mob_type = 1; - } - - if (npc->IsRaidTarget()) { - mob_type = 2; - } - - global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(mob_type, mob_level); + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); if (!scale_data.level) { Log(Logs::General, Logs::NPCScaling, "NPC: %s - scaling data not found for type: %i level: %i", - mob->GetCleanName(), - mob_type, - mob_level + npc->GetCleanName(), + npc_type, + npc_level ); return; @@ -165,16 +151,19 @@ void NpcScaleManager::ScaleMob(Mob *mob) ListStats(npc); } -void NpcScaleManager::ListStats(Mob *mob) +/** + * @param mob + */ +void NpcScaleManager::ListStats(NPC *&npc) { for (const auto &stat : scaling_stats) { std::string variable = StringFormat("modify_stat_%s", stat.c_str()); - if (mob->EntityVariableExists(variable.c_str())) { + if (npc->EntityVariableExists(variable.c_str())) { Log(Logs::Detail, Logs::NPCScaling, "NpcScaleManager::ListStats: %s - %s ", stat.c_str(), - mob->GetEntityVariable(variable.c_str())); + npc->GetEntityVariable(variable.c_str())); } } } @@ -263,13 +252,13 @@ bool NpcScaleManager::LoadScaleData() } /** - * @param mob_type - * @param mob_level + * @param npc_type + * @param npc_level * @return NpcScaleManager::global_npc_scale */ -NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel(int8 mob_type, int mob_level) +NpcScaleManager::global_npc_scale NpcScaleManager::GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level) { - auto iter = npc_global_base_scaling_data.find(std::make_pair(mob_type, mob_level)); + auto iter = npc_global_base_scaling_data.find(std::make_pair(npc_type, npc_level)); if (iter != npc_global_base_scaling_data.end()) { return iter->second; } @@ -417,4 +406,173 @@ uint32 NpcScaleManager::GetClassLevelDamageMod(uint32 level, uint32 npc_class) } return multiplier; +} + +/** + * @param npc + * @return + */ +int8 NpcScaleManager::GetNPCScalingType(NPC *&npc) +{ + std::string npc_name = npc->GetName(); + + if (npc->IsRareSpawn() || npc_name.find('#') != std::string::npos) { + return 1; + } + + if (npc->IsRaidTarget()) { + return 2; + } + + return 0; +} + +/** + * Returns false if scaling data not found + * @param npc + * @return + */ +bool NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically(NPC *&npc) +{ + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); + + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + + if (!scale_data.level) { + Log( + Logs::General, + Logs::NPCScaling, + "NpcScaleManager::ApplyGlobalBaseScalingToNPCStatically NPC: %s - scaling data not found for type: %i level: %i", + npc->GetCleanName(), + npc_type, + npc_level + ); + + return false; + } + + std::string query = StringFormat( + "UPDATE `npc_types` SET " + "AC = %i, " + "hp = %i, " + "Accuracy = %i, " + "slow_mitigation = %i, " + "ATK = %i, " + "STR = %i, " + "STA = %i, " + "DEX = %i, " + "AGI = %i, " + "_INT = %i, " + "WIS = %i, " + "CHA = %i, " + "MR = %i, " + "CR = %i, " + "FR = %i, " + "PR = %i, " + "DR = %i, " + "Corrup = %i, " + "PhR = %i, " + "mindmg = %i, " + "maxdmg = %i, " + "hp_regen_rate = %i, " + "attack_delay = %i, " + "spellscale = %i, " + "healscale = %i, " + "special_abilities = '%s' " + "WHERE `id` = %i", + scale_data.ac, + scale_data.hp, + scale_data.accuracy, + scale_data.slow_mitigation, + scale_data.attack, + scale_data.strength, + scale_data.stamina, + scale_data.dexterity, + scale_data.agility, + scale_data.intelligence, + scale_data.wisdom, + scale_data.charisma, + scale_data.magic_resist, + scale_data.cold_resist, + scale_data.fire_resist, + scale_data.poison_resist, + scale_data.disease_resist, + scale_data.corruption_resist, + scale_data.physical_resist, + scale_data.min_dmg, + scale_data.max_dmg, + scale_data.hp_regen_rate, + scale_data.attack_delay, + scale_data.spell_scale, + scale_data.heal_scale, + EscapeString(scale_data.special_abilities).c_str(), + npc->GetNPCTypeID() + ); + + auto results = database.QueryDatabase(query); + + return results.Success(); +} + +/** + * Returns false if scaling data not found + * @param npc + * @return + */ +bool NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically(NPC *&npc) +{ + int8 npc_type = GetNPCScalingType(npc); + int npc_level = npc->GetLevel(); + + global_npc_scale scale_data = GetGlobalScaleDataForTypeLevel(npc_type, npc_level); + + if (!scale_data.level) { + Log( + Logs::General, + Logs::NPCScaling, + "NpcScaleManager::ApplyGlobalBaseScalingToNPCDynamically NPC: %s - scaling data not found for type: %i level: %i", + npc->GetCleanName(), + npc_type, + npc_level + ); + + return false; + } + + std::string query = StringFormat( + "UPDATE `npc_types` SET " + "AC = 0, " + "hp = 0, " + "Accuracy = 0, " + "slow_mitigation = 0, " + "ATK = 0, " + "STR = 0, " + "STA = 0, " + "DEX = 0, " + "AGI = 0, " + "_INT = 0, " + "WIS = 0, " + "CHA = 0, " + "MR = 0, " + "CR = 0, " + "FR = 0, " + "PR = 0, " + "DR = 0, " + "Corrup = 0, " + "PhR = 0, " + "mindmg = 0, " + "maxdmg = 0, " + "hp_regen_rate = 0, " + "attack_delay = 0, " + "spellscale = 0, " + "healscale = 0, " + "special_abilities = '' " + "WHERE `id` = %i", + npc->GetNPCTypeID() + ); + + auto results = database.QueryDatabase(query); + + return results.Success(); } \ No newline at end of file diff --git a/zone/npc_scale_manager.h b/zone/npc_scale_manager.h index 2178d1c2a..836261bb0 100644 --- a/zone/npc_scale_manager.h +++ b/zone/npc_scale_manager.h @@ -87,14 +87,18 @@ public: "special_abilities" }; - void ScaleMob(Mob *mob); + void ScaleNPC(NPC * npc); bool LoadScaleData(); - global_npc_scale GetGlobalScaleDataForTypeLevel(int8 mob_type, int mob_level); + global_npc_scale GetGlobalScaleDataForTypeLevel(int8 npc_type, int npc_level); std::map, global_npc_scale> npc_global_base_scaling_data; - void ListStats(Mob * mob); + void ListStats(NPC * &npc); + + int8 GetNPCScalingType(NPC * &npc); + bool ApplyGlobalBaseScalingToNPCStatically(NPC * &npc); + bool ApplyGlobalBaseScalingToNPCDynamically(NPC * &npc); uint32 GetClassLevelDamageMod(uint32 level, uint32 npc_class); };