From 7ea77ee027ece15a1c6440e2fc014bf014cc1fb0 Mon Sep 17 00:00:00 2001 From: Kinglykrab <89047260+Kinglykrab@users.noreply.github.com> Date: Wed, 16 Nov 2022 19:51:13 -0500 Subject: [PATCH] [Bots] Add Quest API Support for Limits. (#2522) * [Bots] Add Quest API Support for Limits. # Perl - Add `$client->GetBotCreationLimit()` to Perl. - Add `$client->GetBotCreationLimit(class_id)` to Perl. - Add `$client->GetBotRequiredLevel()` to Perl. - Add `$client->GetBotRequiredLevel(class_id)` to Perl. - Add `$client->GetBotSpawnLimit()` to Perl. - Add `$client->GetBotSpawnLimit(class_id)` to Perl. - Add `$client->SetBotCreationLimit(creation_limit)` to Perl. - Add `$client->SetBotCreationLimit(creation_limit, class_id)` to Perl. - Add `$client->SetBotRequiredLevel(required_level)` to Perl. - Add `$client->SetBotRequiredLevel(required_level, class_id)` to Perl. - Add `$client->SetBotSpawnLimit(spawn_limit)` to Perl. - Add `$client->SetBotSpawnLimit(spawn_limit, class_id)` to Perl. - Add `$entity_list->GetBotListByCharacterID(character_id, class_id)` to Perl. # Lua - Add `client:GetBotCreationLimit()` to Lua. - Add `client:GetBotCreationLimit(class_id)` to Lua. - Add `client:GetBotRequiredLevel()` to Lua. - Add `client:GetBotRequiredLevel(class_id)` to Lua. - Add `client:GetBotSpawnLimit()` to Lua. - Add `client:GetBotSpawnLimit(class_id)` to Lua. - Add `client:SetBotCreationLimit(creation_limit)` to Lua. - Add `client:SetBotCreationLimit(creation_limit, class_id)` to Lua. - Add `client:SetBotRequiredLevel(required_level)` to Lua. - Add `client:SetBotRequiredLevel(required_level, class_id)` to Lua. - Add `client:SetBotSpawnLimit(spawn_limit)` to Lua. - Add `client:SetBotSpawnLimit(spawn_limit, class_id)` to Lua. - Add `entity_list:GetBotListByCharacterID(character_id, class_id)` to Lua. # Notes - Allows operators to set creation and spawn limits based on class, as well as required level. - Using the class-inspecific methods sets the global limit or required level. - Global limits are checked prior to class-specific limits and if they are not met, creation or spawn is disallowed. - Modified preexisting Quest API to make use of this new stuff under the hood. * Update bot_command.cpp * Add client bot file. --- zone/CMakeLists.txt | 1 + zone/bot.cpp | 49 ++- zone/bot.h | 2 +- zone/bot_command.cpp | 756 +++++++++++++++++++++++++++++---------- zone/bot_database.cpp | 104 ++++-- zone/bot_database.h | 11 +- zone/client.cpp | 20 -- zone/client.h | 7 + zone/client_bot.cpp | 159 ++++++++ zone/embparser_api.cpp | 16 +- zone/entity.cpp | 11 +- zone/entity.h | 2 +- zone/lua_client.cpp | 100 ++++++ zone/lua_client.h | 17 + zone/lua_entity_list.cpp | 15 + zone/lua_entity_list.h | 1 + zone/perl_client.cpp | 88 +++++ zone/perl_entity.cpp | 14 +- zone/questmgr.cpp | 176 +++++++-- zone/questmgr.h | 4 +- 20 files changed, 1264 insertions(+), 289 deletions(-) create mode 100644 zone/client_bot.cpp diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 502006c58..783cc5030 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -16,6 +16,7 @@ SET(zone_sources botspellsai.cpp cheat_manager.cpp client.cpp + client_bot.cpp client_mods.cpp client_packet.cpp client_process.cpp diff --git a/zone/bot.cpp b/zone/bot.cpp index 0ca9db626..a974b5799 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -172,10 +172,25 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SetRangerAutoWeaponSelect(false); bool stance_flag = false; - if (!database.botdb.LoadStance(this, stance_flag) && bot_owner) - bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::LoadStance(), GetCleanName()); - if (!stance_flag && bot_owner) - bot_owner->Message(Chat::White, "Could not locate stance for '%s'", GetCleanName()); + if (!database.botdb.LoadStance(this, stance_flag) && bot_owner) { + bot_owner->Message( + Chat::White, + fmt::format( + "Failed to load stance for '{}'.", + GetCleanName() + ).c_str() + ); + } + + if (!stance_flag && bot_owner) { + bot_owner->Message( + Chat::White, + fmt::format( + "Could not locate stance for '{}'.", + GetCleanName() + ).c_str() + ); + } SetTaunting((GetClass() == WARRIOR || GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) && (GetBotStance() == EQ::constants::stanceAggressive)); SetPauseAI(false); @@ -1812,8 +1827,16 @@ bool Bot::Save() bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::SaveBuffs(), GetCleanName()); if (!database.botdb.SaveTimers(this)) bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::SaveTimers(), GetCleanName()); - if (!database.botdb.SaveStance(this)) - bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::SaveStance(), GetCleanName()); + + if (!database.botdb.SaveStance(this)) { + bot_owner->Message( + Chat::White, + fmt::format( + "Failed to save stance for '{}'.", + GetCleanName() + ).c_str() + ); + } if (!SavePet()) bot_owner->Message(Chat::White, "Failed to save pet for '%s'", GetCleanName()); @@ -4200,13 +4223,15 @@ bool Bot::GroupHasBot(Group* group) { return Result; } -uint32 Bot::SpawnedBotCount(uint32 botOwnerCharacterID) { - uint32 Result = 0; - if(botOwnerCharacterID > 0) { - std::list SpawnedBots = entity_list.GetBotsByBotOwnerCharacterID(botOwnerCharacterID); - Result = SpawnedBots.size(); +uint32 Bot::SpawnedBotCount(const uint32 owner_id, uint8 class_id) { + uint32 spawned_bot_count = 0; + + if (owner_id) { + const auto& sbl = entity_list.GetBotListByCharacterID(owner_id, class_id); + spawned_bot_count = sbl.size(); } - return Result; + + return spawned_bot_count; } void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) { diff --git a/zone/bot.h b/zone/bot.h index d89899bb0..6210c90ca 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -375,7 +375,7 @@ public: // Static Class Methods //static void DestroyBotRaidObjects(Client* client); // Can be removed after bot raids are dumped static Bot* LoadBot(uint32 botID); - static uint32 SpawnedBotCount(uint32 botOwnerCharacterID); + static uint32 SpawnedBotCount(const uint32 owner_id, uint8 class_id = 0); static void LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp); //static bool SetBotOwnerCharacterID(uint32 botID, uint32 botOwnerCharacterID, std::string* error_message); static bool IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 5b94b8c72..7fb10a6a8 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -45,6 +45,7 @@ #define strcasecmp _stricmp #endif +#include "../common/data_verification.h" #include "../common/global_define.h" #include "../common/eq_packet.h" #include "../common/features.h" @@ -5063,33 +5064,63 @@ void bot_subcommand_bot_camp(Client *c, const Seperator *sep) void bot_subcommand_bot_clone(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_subcommand_bot_clone", sep->arg[0], "botclone")) + if (helper_command_alias_fail(c, "bot_subcommand_bot_clone", sep->arg[0], "botclone")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [clone_name]", sep->arg[0]); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [clone_name]", + sep->arg[0] + ).c_str() + ); return; } auto my_bot = ActionableBots::AsTarget_ByBot(c); if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); + c->Message(Chat::White, "You must target a bot that you own to use this command!"); return; } + if (!my_bot->GetBotID()) { - c->Message(Chat::White, "An unknown error has occured - BotName: %s, BotID: %u", my_bot->GetCleanName(), my_bot->GetBotID()); - LogCommands("bot_command_clone(): - Error: Active bot reported invalid ID (BotName: [{}], BotID: [{}], OwnerName: [{}], OwnerID: [{}], AcctName: [{}], AcctID: [{}])", - my_bot->GetCleanName(), my_bot->GetBotID(), c->GetCleanName(), c->CharacterID(), c->AccountName(), c->AccountID()); + c->Message( + Chat::White, + fmt::format( + "An unknown error has occured with {} (Bot ID {}).", + my_bot->GetCleanName(), + my_bot->GetBotID() + ).c_str() + ); + LogCommands( + "bot_command_clone(): - Error: Active bot reported invalid ID (BotName: [{}], BotID: [{}], OwnerName: [{}], OwnerID: [{}], AcctName: [{}], AcctID: [{}])", + my_bot->GetCleanName(), + my_bot->GetBotID(), + c->GetCleanName(), + c->CharacterID(), + c->AccountName(), + c->AccountID() + ); return; } if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must [name] your bot clone"); + c->Message(Chat::White, "You must name your bot clone."); return; } + std::string bot_name = sep->arg[1]; if (!Bot::IsValidName(bot_name)) { - c->Message(Chat::White, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "'{}' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'.", + bot_name + ).c_str() + ); return; } @@ -5097,42 +5128,126 @@ void bot_subcommand_bot_clone(Client *c, const Seperator *sep) bool available_flag = false; if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { - c->Message(Chat::White, "%s", BotDatabase::fail::QueryNameAvailablity()); - return; - } - if (!available_flag) { - c->Message(Chat::White, "The name %s is already being used. Please choose a different name", bot_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "Failed to query name availability for '{}'.", + bot_name + ).c_str() + ); return; } - uint32 max_bot_count = RuleI(Bots, CreationLimit); + if (!available_flag) { + c->Message( + Chat::White, + fmt::format( + "The name '{}' is already being used. Please choose a different name.", + bot_name + ).c_str() + ); + return; + } + + auto bot_creation_limit = c->GetBotCreationLimit(); + auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass()); uint32 bot_count = 0; - if (!database.botdb.QueryBotCount(c->CharacterID(), bot_count)) { - c->Message(Chat::White, "%s", BotDatabase::fail::QueryBotCount()); + uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) { + c->Message(Chat::White, "Failed to query bot count."); return; } - if (bot_count >= max_bot_count) { - c->Message(Chat::White, "You have reached the maximum limit of %i bots", max_bot_count); + + if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { + std::string message; + + if (bot_creation_limit) { + message = fmt::format( + "You have reached the maximum limit of {} bot{}.", + bot_creation_limit, + bot_creation_limit != 1 ? "s" : "" + ); + } else { + message = "You cannot create any bots."; + } + + c->Message(Chat::White, message.c_str()); + return; + } + + if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { + std::string message; + + if (bot_creation_limit_class) { + message = fmt::format( + "You cannot create anymore than {} {} bot{}.", + bot_creation_limit_class, + GetClassIDName(my_bot->GetClass()), + bot_creation_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You cannot create any {} bots.", + GetClassIDName(my_bot->GetClass()) + ); + } + + c->Message(Chat::White, message.c_str()); return; } uint32 clone_id = 0; if (!database.botdb.CreateCloneBot(c->CharacterID(), my_bot->GetBotID(), bot_name, clone_id) || !clone_id) { - c->Message(Chat::White, "%s '%s'", BotDatabase::fail::CreateCloneBot(), bot_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "Failed to create clone bot '{}'.", + bot_name + ).c_str() + ); return; } int clone_stance = EQ::constants::stancePassive; - if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) - c->Message(Chat::White, "%s for bot '%s'", BotDatabase::fail::LoadStance(), my_bot->GetCleanName()); - if (!database.botdb.SaveStance(clone_id, clone_stance)) - c->Message(Chat::White, "%s for clone '%s'", BotDatabase::fail::SaveStance(), bot_name.c_str()); + if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { + c->Message( + Chat::White, + fmt::format( + "Failed to load stance from '{}'.", + my_bot->GetCleanName() + ).c_str() + ); + } - if (!database.botdb.CreateCloneBotInventory(c->CharacterID(), my_bot->GetBotID(), clone_id)) - c->Message(Chat::White, "%s for clone '%s'", BotDatabase::fail::CreateCloneBotInventory(), bot_name.c_str()); + if (!database.botdb.SaveStance(clone_id, clone_stance)) { + c->Message( + Chat::White, + fmt::format( + "Failed to save stance for clone '{}'.", + bot_name + ).c_str() + ); + } - c->Message(Chat::White, "Bot '%s' was successfully cloned to bot '%s'", my_bot->GetCleanName(), bot_name.c_str()); + if (!database.botdb.CreateCloneBotInventory(c->CharacterID(), my_bot->GetBotID(), clone_id)) { + c->Message( + Chat::White, + fmt::format( + "Failed to create clone bot inventory for clone '{}'.", + bot_name + ).c_str() + ); + } + + c->Message( + Chat::White, + fmt::format( + "Bot Cloned | From: {} To: {}", + my_bot->GetCleanName(), + bot_name + ).c_str() + ); } void bot_command_view_combos(Client *c, const Seperator *sep) @@ -5199,21 +5314,24 @@ void bot_command_view_combos(Client *c, const Seperator *sep) void bot_subcommand_bot_create(Client *c, const Seperator *sep) { - const std::string class_substrs[17] = { "", - "%u (WAR)", "%u (CLR)", "%u (PAL)", "%u (RNG)", - "%u (SHD)", "%u (DRU)", "%u (MNK)", "%u (BRD)", - "%u (ROG)", "%u (SHM)", "%u (NEC)", "%u (WIZ)", - "%u (MAG)", "%u (ENC)", "%u (BST)", "%u (BER)" + const std::string class_substrs[17] = { + "", + "{} (WAR)", "{} (CLR)", "{} (PAL)", "{} (RNG)", + "{} (SHD)", "{} (DRU)", "{} (MNK)", "{} (BRD)", + "{} (ROG)", "{} (SHM)", "{} (NEC)", "{} (WIZ)", + "{} (MAG)", "{} (ENC)", "{} (BST)", "{} (BER)" }; - const std::string race_substrs[17] = { "", - "%u (HUM)", "%u (BAR)", "%u (ERU)", "%u (ELF)", - "%u (HIE)", "%u (DEF)", "%u (HEF)", "%u (DWF)", - "%u (TRL)", "%u (OGR)", "%u (HFL)", "%u (GNM)", - "%u (IKS)", "%u (VAH)", "%u (FRG)", "%u (DRK)" + const std::string race_substrs[17] = { + "", + "{} (HUM)", "{} (BAR)", "{} (ERU)", "{} (ELF)", + "{} (HIE)", "{} (DEF)", "{} (HEF)", "{} (DWF)", + "{} (TRL)", "{} (OGR)", "{} (HFL)", "{} (GNM)", + "{} (IKS)", "{} (VAH)", "{} (FRG)", "{} (DRK)" }; - const uint16 race_values[17] = { 0, + const uint16 race_values[17] = { + 0, HUMAN, BARBARIAN, ERUDITE, WOOD_ELF, HIGH_ELF, DARK_ELF, HALF_ELF, DWARF, TROLL, OGRE, HALFLING, GNOME, @@ -5221,86 +5339,137 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) }; const std::string gender_substrs[2] = { - "%u (M)", "%u (F)", + "{} (M)", "{} (F)", }; - if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate")) + if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate")) { return; + } + if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [bot_name] [bot_class] [bot_race] [bot_gender]", sep->arg[0]); - std::string window_title = "Bot Create Options"; + c->Message( + Chat::White, + fmt::format( + "Usage: {} [bot_name] [bot_class] [bot_race] [bot_gender]", + sep->arg[0] + ).c_str() + ); + std::string window_text; std::string message_separator; int object_count = 0; const int object_max = 5; window_text.append("Classes:"); + message_separator = " "; object_count = 1; for (int i = 0; i <= 15; ++i) { window_text.append(message_separator); + if (object_count >= object_max) { window_text.append("
"); object_count = 0; } - window_text.append(StringFormat(class_substrs[i + 1].c_str(), (i + 1))); + + window_text.append( + fmt::format( + class_substrs[i + 1], + (i + 1) + ) + ); + ++object_count; message_separator = ", "; } + window_text.append("

"); window_text.append("Races:"); + message_separator = " "; object_count = 1; for (int i = 0; i <= 15; ++i) { window_text.append(message_separator); + if (object_count >= object_max) { window_text.append("
"); object_count = 0; } - window_text.append(StringFormat(race_substrs[i + 1].c_str(), race_values[i + 1])); + + window_text.append( + fmt::format( + race_substrs[i + 1], + race_values[i + 1] + ) + ); + ++object_count; message_separator = ", "; } + window_text.append("

"); window_text.append("Genders:"); + message_separator = " "; for (int i = 0; i <= 1; ++i) { window_text.append(message_separator); - window_text.append(StringFormat(gender_substrs[i].c_str(), i)); + + window_text.append( + fmt::format( + gender_substrs[i], + i + ) + ); + message_separator = ", "; } - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + c->SendPopupToClient("Bot Create Options", window_text.c_str()); return; } - if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must [name] your bot"); + auto arguments = sep->argnum; + + if (!arguments || sep->IsNumber(1)) { + c->Message(Chat::White, "You must name your bot!"); return; } + std::string bot_name = sep->arg[1]; bot_name = Strings::UcFirst(bot_name); - if (sep->arg[2][0] == '\0' || !sep->IsNumber(2)) { - c->Message(Chat::White, "Invalid Class!"); + if (arguments < 2 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Invalid class!"); return; } - uint8 bot_class = atoi(sep->arg[2]); - if (sep->arg[3][0] == '\0' || !sep->IsNumber(3)) { - c->Message(Chat::White, "Invalid Race!"); - return; - } - uint16 bot_race = atoi(sep->arg[3]); + auto bot_class = static_cast(std::stoul(sep->arg[2])); - if (sep->arg[4][0] == '\0') { - c->Message(Chat::White, "Invalid Gender!"); + if (arguments < 3 || !sep->IsNumber(3)) { + c->Message(Chat::White, "Invalid race!"); return; } - uint8 bot_gender = atoi(sep->arg[4]); + + auto bot_race = static_cast(std::stoul(sep->arg[3])); + + if (arguments < 4) { + c->Message(Chat::White, "Invalid gender!"); + return; + } + + auto bot_gender = 0; + + if (sep->IsNumber(4)) { + bot_gender = static_cast(std::stoul(sep->arg[4])); + } else { + if (!strcasecmp(sep->arg[4], "m") || !strcasecmp(sep->arg[4], "male")) { + bot_gender = 0; + } else if (!strcasecmp(sep->arg[4], "f") || !strcasecmp(sep->arg[4], "female")) { + bot_gender = 1; + } + } helper_bot_create(c, bot_name, bot_class, bot_race, bot_gender); } @@ -5951,10 +6120,6 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) } auto* bot = entity_list.GetBotByBotName(bots_iter.Name); - auto bot_spawn_saylink = Saylink::Silent( - fmt::format("^spawn {}", bots_iter.Name), - bots_iter.Name - ); c->Message( Chat::White, @@ -5963,7 +6128,10 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) bot_number, ( (c->CharacterID() == bots_iter.Owner_ID && !bot) ? - bot_spawn_saylink : + Saylink::Silent( + fmt::format("^spawn {}", bots_iter.Name), + bots_iter.Name + ) : bots_iter.Name ), bots_iter.Level, @@ -6000,14 +6168,34 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) c->Message(Chat::White, "Note: You can spawn any owned bots by clicking their name if they are not already spawned."); + c->Message(Chat::White, "Your bot creation limits are as follows:"); + + const auto overall_bot_creation_limit = c->GetBotCreationLimit(); + c->Message( Chat::White, fmt::format( - "Your bot creation limit is {} bot{}.", - RuleI(Bots, CreationLimit), - RuleI(Bots, CreationLimit) != 1 ? "s" : "" + "Overall | {} Bot{}", + overall_bot_creation_limit, + overall_bot_creation_limit != 1 ? "s" : "" ).c_str() ); + + for (uint8 class_id = WARRIOR; class_id <= BERSERKER; class_id++) { + auto class_creation_limit = c->GetBotCreationLimit(class_id); + + if (class_creation_limit != overall_bot_creation_limit) { + c->Message( + Chat::White, + fmt::format( + "{} | {} Bot{}", + GetClassIDName(class_id), + class_creation_limit, + class_creation_limit != 1 ? "s" : "" + ).c_str() + ); + } + } } } @@ -6179,55 +6367,63 @@ void bot_subcommand_bot_report(Client *c, const Seperator *sep) void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) { - if (helper_command_alias_fail(c, "bot_subcommand_bot_spawn", sep->arg[0], "botspawn")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [bot_name]", sep->arg[0]); + if (helper_command_alias_fail(c, "bot_subcommand_bot_spawn", sep->arg[0], "botspawn")) { return; } - int rule_level = RuleI(Bots, BotCharacterLevel); - if (c->GetLevel() < rule_level) { - c->Message(Chat::White, "You must be level %i to use bots", rule_level); + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [bot_name]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto bot_character_level = c->GetBotRequiredLevel(); + if ( + bot_character_level >= 0 && + c->GetLevel() < bot_character_level && + !c->GetGM() + ) { + c->Message( + Chat::White, + fmt::format( + "You must be level {} to spawn bots.", + bot_character_level + ).c_str() + ); return; } if (c->GetFeigned()) { - c->Message(Chat::White, "You can not spawn a bot while feigned"); + c->Message(Chat::White, "You cannot spawn a bot while feigned."); return; } - int spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); + auto bot_spawn_limit = c->GetBotSpawnLimit(); + auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); - int rule_limit = RuleI(Bots, SpawnLimit); - if (spawned_bot_count >= rule_limit && !c->GetGM()) { - c->Message(Chat::White, "You can not have more than %i spawned bots", rule_limit); - return; - } - - if (RuleB(Bots, QuestableSpawnLimit) && !c->GetGM()) { - int allowed_bot_count = 0; - if (!database.botdb.LoadQuestableSpawnCount(c->CharacterID(), allowed_bot_count)) { - c->Message(Chat::White, "Failed to load questable spawn count."); - return; - } - - if (!allowed_bot_count) { - c->Message(Chat::White, "You are not currently allowed to spawn any bots."); - return; - } - - if (spawned_bot_count >= allowed_bot_count) { - c->Message( - Chat::White, - fmt::format( - "You have reached your current limit of {} spawned bot{}.", - allowed_bot_count, - allowed_bot_count != 1 ? "s" : "" - ).c_str() + if ( + bot_spawn_limit >= 0 && + spawned_bot_count >= bot_spawn_limit && + !c->GetGM() + ) { + std::string message; + if (bot_spawn_limit) { + message = fmt::format( + "You cannot have more than {} spawned bot{}.", + bot_spawn_limit, + bot_spawn_limit != 1 ? "s" : "" ); - return; + } else { + message = "You are not currently allowed to spawn any bots."; } + + c->Message(Chat::White, message.c_str()); + return; } if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { @@ -6238,17 +6434,82 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) std::string bot_name = sep->arg[1]; uint32 bot_id = 0; - if (!database.botdb.LoadBotID(c->CharacterID(), bot_name, bot_id)) { - c->Message(Chat::White, "%s for '%s'", BotDatabase::fail::LoadBotID(), bot_name.c_str()); + uint8 bot_class = 0; + if (!database.botdb.LoadBotID(c->CharacterID(), bot_name, bot_id, bot_class)) { + c->Message( + Chat::White, + fmt::format( + "Failed to load bot ID for '{}'.", + bot_name + ).c_str() + ); return; } + + auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class); + auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class); + + if ( + bot_spawn_limit_class >= 0 && + spawned_bot_count_class >= bot_spawn_limit_class && + !c->GetGM() + ) { + std::string message; + + if (bot_spawn_limit_class) { + message = fmt::format( + "You cannot have more than {} spawned {} bot{}.", + bot_spawn_limit_class, + GetClassIDName(bot_class), + bot_spawn_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You are not currently allowed to spawn any {} bots.", + GetClassIDName(bot_class) + ); + } + + c->Message(Chat::White, message.c_str()); + return; + } + + auto bot_character_level_class = c->GetBotRequiredLevel(bot_class); + if ( + bot_character_level_class >= 0 && + c->GetLevel() < bot_character_level_class && + !c->GetGM() + ) { + c->Message( + Chat::White, + fmt::format( + "You must be level {} to spawn {} bots.", + bot_character_level_class, + GetClassIDName(bot_class) + ).c_str() + ); + return; + } + if (!bot_id) { - c->Message(Chat::White, "You don't own a bot named '%s'", bot_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "You don't own a bot named '{}'.", + bot_name + ).c_str() + ); return; } if (entity_list.GetMobByBotID(bot_id)) { - c->Message(Chat::White, "'%s' is already spawned in zone", bot_name.c_str()); + c->Message( + Chat::White, + fmt::format( + "'{}' is already spawned.", + bot_name + ).c_str() + ); return; } @@ -6257,39 +6518,61 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) std::list group_list; c->GetGroup()->GetMemberList(group_list); for (auto member_iter : group_list) { - if (!member_iter) + if (!member_iter) { continue; - if (member_iter->qglobal) // what is this?? really should have had a message to describe failure... (can't spawn bots if you are assigned to a task/instance?) + } + + if (member_iter->qglobal) { // what is this?? really should have had a message to describe failure... (can't spawn bots if you are assigned to a task/instance?) return; - if (!member_iter->qglobal && (member_iter->GetAppearance() != eaDead) && (member_iter->IsEngaged() || (member_iter->IsClient() && member_iter->CastToClient()->GetAggroCount()))) { - c->Message(Chat::White, "You can't summon bots while you are engaged."); + } + + if ( + !member_iter->qglobal && + member_iter->GetAppearance() != eaDead && + ( + member_iter->IsEngaged() || + ( + member_iter->IsClient() && + member_iter->CastToClient()->GetAggroCount() + ) + ) + ) { + c->Message(Chat::White, "You cannot summon bots while you are engaged."); return; } } - } - else if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You can't spawn bots while you are engaged."); + } else if (c->GetAggroCount()) { + c->Message(Chat::White, "You cannot spawn bots while you are engaged."); return; } - //if (c->IsEngaged()) { - // c->Message(Chat::White, "You can't spawn bots while you are engaged."); - // return; - //} - auto my_bot = Bot::LoadBot(bot_id); if (!my_bot) { - c->Message(Chat::White, "No valid bot '%s' (id: %i) exists", bot_name.c_str(), bot_id); + c->Message( + Chat::White, + fmt::format( + "Invalid bot '{}' (ID {})", + bot_name, + bot_id + ).c_str() + ); return; } if (!my_bot->Spawn(c)) { - c->Message(Chat::White, "Failed to spawn bot '%s' (id: %i)", bot_name.c_str(), bot_id); + c->Message( + Chat::White, + fmt::format( + "Failed to spawn '{}' (ID {})", + bot_name, + bot_id + ).c_str() + ); safe_delete(my_bot); return; } - static const char* bot_spawn_message[17] = { + static std::string bot_spawn_message[17] = { "I am ready to fight!", // DEFAULT "A solid weapon is my ally!", // WARRIOR "The pious shall never die!", // CLERIC @@ -6315,10 +6598,16 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) } if (c->GetBotOption(Client::booSpawnMessageSay)) { - Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[message_index]); - } - else if (c->GetBotOption(Client::booSpawnMessageTell)) { - c->Message(Chat::Tell, "%s tells you, \"%s\"", my_bot->GetCleanName(), bot_spawn_message[message_index]); + Bot::BotGroupSay(my_bot, bot_spawn_message[message_index].c_str()); + } else if (c->GetBotOption(Client::booSpawnMessageTell)) { + c->Message( + Chat::Tell, + fmt::format( + "{} tells you, \"{}\"", + my_bot->GetCleanName(), + bot_spawn_message[message_index] + ).c_str() + ); } } @@ -7380,13 +7669,13 @@ void bot_subcommand_botgroup_load(Client *c, const Seperator *sep) for (auto member_iter : member_list) { if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You can't spawn bots while your group is engaged,"); + c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); return; } } } else { if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You can't spawn bots while you are engaged,"); + c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); return; } } @@ -7426,43 +7715,28 @@ void bot_subcommand_botgroup_load(Client *c, const Seperator *sep) return; } - int spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); + auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); - if (RuleB(Bots, QuestableSpawnLimit)) { - int allowed_bot_count = 0; - if (!database.botdb.LoadQuestableSpawnCount(c->CharacterID(), allowed_bot_count)) { - c->Message(Chat::White, "Failed to load questable spawn count."); - return; - } - - if (!allowed_bot_count) { - c->Message(Chat::White, "You can not spawn any bots"); - return; - } - - if (spawned_bot_count >= allowed_bot_count || (spawned_bot_count + member_list.begin()->second.size()) > allowed_bot_count) { - c->Message( - Chat::White, - fmt::format( - "You can not spawn more than {} bot{}.", - allowed_bot_count, - allowed_bot_count != 1 ? "s" : "" - ).c_str() + auto bot_spawn_limit = c->GetBotSpawnLimit(); + if ( + bot_spawn_limit >= 0 && + ( + spawned_bot_count >= bot_spawn_limit || + (spawned_bot_count + member_list.begin()->second.size()) > bot_spawn_limit + ) + ) { + std::string message; + if (bot_spawn_limit) { + message = fmt::format( + "You cannot have more than {} spawned bot{}.", + bot_spawn_limit, + bot_spawn_limit != 1 ? "s" : "" ); - return; + } else { + message = "You are not currently allowed to spawn any bots."; } - } - const int allowed_bot_limit = RuleI(Bots, SpawnLimit); - if (spawned_bot_count >= allowed_bot_limit || (spawned_bot_count + member_list.begin()->second.size()) > allowed_bot_limit) { - c->Message( - Chat::White, - fmt::format( - "You can not spawn more than {} bot{}.", - allowed_bot_limit, - allowed_bot_limit != 1 ? "s" : "" - ).c_str() - ); + c->Message(Chat::White, message.c_str()); return; } @@ -9320,27 +9594,52 @@ void helper_bot_appearance_form_update(Bot *my_bot) uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender) { uint32 bot_id = 0; - if (!bot_owner) + if (!bot_owner) { return bot_id; + } + if (!Bot::IsValidName(bot_name)) { - bot_owner->Message(Chat::White, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str()); + bot_owner->Message( + Chat::White, + fmt::format( + "'{}' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'.", + bot_name + ).c_str() + ); return bot_id; } bool available_flag = false; if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { - bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::QueryNameAvailablity(), bot_name.c_str()); - return bot_id; - } - if (!available_flag) { - bot_owner->Message(Chat::White, "The name %s is already being used. Please choose a different name", bot_name.c_str()); + bot_owner->Message( + Chat::White, + fmt::format( + "Failed to query name availability for '{}'.", + bot_name + ).c_str() + ); return bot_id; } - if (!Bot::IsValidRaceClassCombo(bot_race, bot_class)) { - const char* bot_race_name = GetRaceIDName(bot_race); - const char* bot_class_name = GetClassIDName(bot_class); - std::string view_saylink = Saylink::Silent(fmt::format("^viewcombos {}", bot_race), "view"); + if (!available_flag) { + bot_owner->Message( + Chat::White, + fmt::format( + "The name '{}' is already being used. Please choose a different name", + bot_name + ).c_str() + ); + return bot_id; + } + + if (!Bot::IsValidRaceClassCombo(bot_race, bot_class) && bot_owner->IsPlayerRace(bot_race)) { + const std::string bot_race_name = GetRaceIDName(bot_race); + const std::string bot_class_name = GetClassIDName(bot_class); + const auto view_saylink = Saylink::Silent( + fmt::format("^viewcombos {}", bot_race), + "view" + ); + bot_owner->Message( Chat::White, fmt::format( @@ -9351,35 +9650,130 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bot_race_name ).c_str() ); + return bot_id; } - if (bot_gender > FEMALE) { - bot_owner->Message(Chat::White, "gender: %u (M), %u (F)", MALE, FEMALE); + if (!EQ::ValueWithin(bot_gender, MALE, FEMALE)) { + bot_owner->Message( + Chat::White, + fmt::format( + "Gender: {} ({}) or {} ({})", + GetGenderName(MALE), + MALE, + GetGenderName(FEMALE), + FEMALE + ).c_str() + ); return bot_id; } - uint32 max_bot_count = RuleI(Bots, CreationLimit); + auto bot_creation_limit = bot_owner->GetBotCreationLimit(); + auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class); uint32 bot_count = 0; - if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_count)) { - bot_owner->Message(Chat::White, "%s", BotDatabase::fail::QueryBotCount()); + uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) { + bot_owner->Message(Chat::White, "Failed to query bot count."); return bot_id; } - if (bot_count >= max_bot_count) { - bot_owner->Message(Chat::White, "You have reached the maximum limit of %i bots.", max_bot_count); + + if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { + std::string message; + + if (bot_creation_limit) { + message = fmt::format( + "You cannot create anymore than {} bot{}.", + bot_creation_limit, + bot_creation_limit != 1 ? "s" : "" + ); + } else { + message = "You cannot create any bots."; + } + + bot_owner->Message(Chat::White, message.c_str()); return bot_id; } + if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { + std::string message; + + if (bot_creation_limit_class) { + message = fmt::format( + "You cannot create anymore than {} {} bot{}.", + bot_creation_limit_class, + GetClassIDName(bot_class), + bot_creation_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You cannot create any {} bots.", + GetClassIDName(bot_class) + ); + } + + bot_owner->Message(Chat::White, message.c_str()); + return bot_id; + } + + auto bot_character_level = bot_owner->GetBotRequiredLevel(); + + if ( + bot_character_level >= 0 && + bot_owner->GetLevel() < bot_character_level + ) { + bot_owner->Message( + Chat::White, + fmt::format( + "You must be level {} to use bots.", + bot_character_level + ).c_str() + ); + return bot_id; + } + + auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class); + + if ( + bot_character_level_class >= 0 && + bot_owner->GetLevel() < bot_character_level_class + ) { + bot_owner->Message( + Chat::White, + fmt::format( + "You must be level {} to use {} bots.", + bot_character_level_class, + GetClassIDName(bot_class) + ).c_str() + ); + return bot_id; + } + + auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name.c_str(), "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner); if (!my_bot->Save()) { - bot_owner->Message(Chat::White, "Failed to create '%s' due to unknown cause", my_bot->GetCleanName()); + bot_owner->Message( + Chat::White, + fmt::format( + "Failed to create '{}' due to unknown cause.", + my_bot->GetCleanName() + ).c_str() + ); safe_delete(my_bot); return bot_id; } - bot_owner->Message(Chat::White, "Successfully created '%s' (id: %u)", my_bot->GetCleanName(), my_bot->GetBotID()); + bot_owner->Message( + Chat::White, + fmt::format( + "Bot Created | Name: {} ID: {} Race: {} Class: {}", + my_bot->GetCleanName(), + my_bot->GetBotID(), + GetRaceIDName(my_bot->GetRace()), + GetClassIDName(my_bot->GetClass()) + ).c_str() + ); bot_id = my_bot->GetBotID(); safe_delete(my_bot); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 605e5d286..3b58dca21 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -189,38 +189,46 @@ bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& availa return true; } -bool BotDatabase::QueryBotCount(const uint32 owner_id, uint32& bot_count) +bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count) { - if (!owner_id) + if (!owner_id) { return false; + } - query = StringFormat("SELECT COUNT(`bot_id`) FROM `bot_data` WHERE `owner_id` = '%i'", owner_id); + query = fmt::format( + "SELECT COUNT(`bot_id`) FROM `bot_data` WHERE `owner_id` = {}", + owner_id + ); auto results = database.QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return false; - if (!results.RowCount()) + } + + if (!results.RowCount()) { return true; + } auto row = results.begin(); - bot_count = atoi(row[0]); + bot_count = std::stoul(row[0]); - return true; -} + if (EQ::ValueWithin(class_id, WARRIOR, BERSERKER)) { + query = fmt::format( + "SELECT COUNT(`bot_id`) FROM `bot_data` WHERE `owner_id` = {} AND `class` = {}", + owner_id, + class_id + ); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } -bool BotDatabase::LoadQuestableSpawnCount(const uint32 owner_id, int& spawn_count) -{ - if (!owner_id) - return false; + if (!results.RowCount()) { + return true; + } - query = StringFormat("SELECT `value` FROM `quest_globals` WHERE `name` = 'bot_spawn_limit' AND `charid` = '%i' LIMIT 1", owner_id); - auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls - if (!results.Success()) - return false; - if (!results.RowCount()) - return true; - - auto row = results.begin(); - spawn_count = atoi(row[0]); + auto row = results.begin(); + bot_class_count = std::stoul(row[0]); + } return true; } @@ -310,21 +318,55 @@ bool BotDatabase::LoadOwnerID(const uint32 bot_id, uint32& owner_id) bool BotDatabase::LoadBotID(const uint32 owner_id, const std::string& bot_name, uint32& bot_id) { - if (!owner_id || bot_name.empty()) + if (!owner_id || bot_name.empty()) { return false; + } - query = StringFormat( - "SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u' AND `name` = '%s' LIMIT 1", - owner_id, bot_name.c_str() + query = fmt::format( + "SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = {} AND `name` = '{}' LIMIT 1", + owner_id, + bot_name ); + auto results = database.QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return false; - if (!results.RowCount()) + } + + if (!results.RowCount()) { return true; + } auto row = results.begin(); - bot_id = atoi(row[0]); + bot_id = std::stoul(row[0]); + + return true; +} + +bool BotDatabase::LoadBotID(const uint32 owner_id, const std::string& bot_name, uint32& bot_id, uint8& bot_class_id) +{ + if (!owner_id || bot_name.empty()) { + return false; + } + + query = fmt::format( + "SELECT `bot_id`, `class` FROM `bot_data` WHERE `owner_id` = {} AND `name` = '{}' LIMIT 1", + owner_id, + bot_name + ); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } + + if (!results.RowCount()) { + return true; + } + + auto row = results.begin(); + bot_id = std::stoul(row[0]); + bot_class_id = static_cast(std::stoul(row[1])); return true; } @@ -3211,8 +3253,6 @@ bool BotDatabase::SaveExpansionBitmask(const uint32 bot_id, const int expansion_ } /* fail::Bot functions */ -const char* BotDatabase::fail::QueryNameAvailablity() { return "Failed to query name availability"; } -const char* BotDatabase::fail::QueryBotCount() { return "Failed to query bot count"; } const char* BotDatabase::fail::LoadBotsList() { return "Failed to bots list"; } const char* BotDatabase::fail::LoadOwnerID() { return "Failed to load owner ID"; } const char* BotDatabase::fail::LoadBotID() { return "Failed to load bot ID"; } @@ -3223,8 +3263,6 @@ const char* BotDatabase::fail::DeleteBot() { return "Failed to delete bot"; } const char* BotDatabase::fail::LoadBuffs() { return "Failed to load buffs"; } const char* BotDatabase::fail::SaveBuffs() { return "Failed to save buffs"; } const char* BotDatabase::fail::DeleteBuffs() { return "Failed to delete buffs"; } -const char* BotDatabase::fail::LoadStance() { return "Failed to load stance"; } -const char* BotDatabase::fail::SaveStance() { return "Failed to save stance"; } const char* BotDatabase::fail::DeleteStance() { return "Failed to delete stance"; } const char* BotDatabase::fail::LoadTimers() { return "Failed to load timers"; } const char* BotDatabase::fail::SaveTimers() { return "Failed to save timers"; } @@ -3271,8 +3309,6 @@ const char* BotDatabase::fail::ToggleHelmAppearance() { return "Failed to save t const char* BotDatabase::fail::ToggleAllHelmAppearances() { return "Failed to save toggle all helm appearance"; } const char* BotDatabase::fail::SaveFollowDistance() { return "Failed to save follow distance"; } const char* BotDatabase::fail::SaveAllFollowDistances() { return "Failed to save all follow distances"; } -const char* BotDatabase::fail::CreateCloneBot() { return "Failed to create clone bot"; } -const char* BotDatabase::fail::CreateCloneBotInventory() { return "Failed to create clone bot inventory"; } const char* BotDatabase::fail::SaveStopMeleeLevel() { return "Failed to save stop melee level"; } /* fail::Bot heal rotation functions */ diff --git a/zone/bot_database.h b/zone/bot_database.h index 1174d0b45..2183c6d02 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -50,13 +50,13 @@ public: /* Bot functions */ bool QueryNameAvailablity(const std::string& bot_name, bool& available_flag); - bool QueryBotCount(const uint32 owner_id, uint32& bot_count); - bool LoadQuestableSpawnCount(const uint32 owner_id, int& spawn_count); + bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count); bool LoadBotsList(const uint32 owner_id, std::list& bots_list, bool ByAccount = false); bool LoadOwnerID(const std::string& bot_name, uint32& owner_id); bool LoadOwnerID(const uint32 bot_id, uint32& owner_id); bool LoadBotID(const uint32 owner_id, const std::string& bot_name, uint32& bot_id); + bool LoadBotID(const uint32 owner_id, const std::string& bot_name, uint32& bot_id, uint8& bot_class_id); bool LoadBot(const uint32 bot_id, Bot*& loaded_bot); bool SaveNewBot(Bot* bot_inst, uint32& bot_id); @@ -196,9 +196,6 @@ public: class fail { public: /* fail::Bot functions */ - static const char* QueryNameAvailablity(); - static const char* QueryBotCount(); - static const char* LoadQuestableSpawnCount(); static const char* LoadBotsList(); static const char* LoadOwnerID(); static const char* LoadBotID(); @@ -209,8 +206,6 @@ public: static const char* LoadBuffs(); static const char* SaveBuffs(); static const char* DeleteBuffs(); - static const char* LoadStance(); - static const char* SaveStance(); static const char* DeleteStance(); static const char* LoadTimers(); static const char* SaveTimers(); @@ -257,8 +252,6 @@ public: static const char* ToggleAllHelmAppearances(); static const char* SaveFollowDistance(); static const char* SaveAllFollowDistances(); - static const char* CreateCloneBot(); - static const char* CreateCloneBotInventory(); static const char* SaveStopMeleeLevel(); /* fail::Bot bot-group functions */ diff --git a/zone/client.cpp b/zone/client.cpp index dd2527dc2..9d886f19b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9558,26 +9558,6 @@ void Client::SetLastPositionBeforeBulkUpdate(glm::vec4 in_last_position_before_b Client::last_position_before_bulk_update = in_last_position_before_bulk_update; } -#ifdef BOTS - -bool Client::GetBotOption(BotOwnerOption boo) const { - - if (boo < _booCount) { - return bot_owner_options[boo]; - } - - return false; -} - -void Client::SetBotOption(BotOwnerOption boo, bool flag) { - - if (boo < _booCount) { - bot_owner_options[boo] = flag; - } -} - -#endif - void Client::SendToGuildHall() { std::string zone_short_name = "guildhall"; diff --git a/zone/client.h b/zone/client.h index 006965212..a2106e33f 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2031,6 +2031,13 @@ public: bool GetBotPrecombat() { return m_bot_precombat; } void SetBotPrecombat(bool flag = true) { m_bot_precombat = flag; } + int GetBotRequiredLevel(uint8 class_id = 0); + uint32 GetBotCreationLimit(uint8 class_id = 0); + int GetBotSpawnLimit(uint8 class_id = 0); + void SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id = 0); + void SetBotRequiredLevel(int new_required_level, uint8 class_id = 0); + void SetBotSpawnLimit(int new_spawn_limit, uint8 class_id = 0); + private: bool bot_owner_options[_booCount]; bool m_bot_pulling; diff --git a/zone/client_bot.cpp b/zone/client_bot.cpp new file mode 100644 index 000000000..e5cda975c --- /dev/null +++ b/zone/client_bot.cpp @@ -0,0 +1,159 @@ +#ifdef BOTS + +#include "client.h" + +bool Client::GetBotOption(BotOwnerOption boo) const { + if (boo < _booCount) { + return bot_owner_options[boo]; + } + + return false; +} + +void Client::SetBotOption(BotOwnerOption boo, bool flag) { + if (boo < _booCount) { + bot_owner_options[boo] = flag; + } +} + +uint32 Client::GetBotCreationLimit(uint8 class_id) +{ + uint32 bot_creation_limit = RuleI(Bots, CreationLimit); + + const auto bucket_name = fmt::format( + "bot_creation_limit{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { + bot_creation_limit = std::stoul(bucket_value); + } + + return bot_creation_limit; +} + +int Client::GetBotRequiredLevel(uint8 class_id) +{ + int bot_character_level = RuleI(Bots, BotCharacterLevel); + + const auto bucket_name = fmt::format( + "bot_required_level{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { + bot_character_level = std::stoi(bucket_value); + } + + return bot_character_level; +} + +int Client::GetBotSpawnLimit(uint8 class_id) +{ + int bot_spawn_limit = RuleI(Bots, SpawnLimit); + + const auto bucket_name = fmt::format( + "bot_spawn_limit{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + auto bucket_value = GetBucket(bucket_name); + if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) { + bot_spawn_limit = std::stoi(bucket_value); + return bot_spawn_limit; + } + + if (RuleB(Bots, QuestableSpawnLimit)) { + const auto query = fmt::format( + "SELECT `value` FROM `quest_globals` WHERE `name` = '{}' AND `charid` = {} LIMIT 1", + bucket_name, + CharacterID() + ); + + auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls + if (!results.Success() || !results.RowCount()) { + return bot_spawn_limit; + } + + auto row = results.begin(); + bot_spawn_limit = std::stoi(row[0]); + } + + return bot_spawn_limit; +} + +void Client::SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id) +{ + const auto bucket_name = fmt::format( + "bot_creation_limit{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + SetBucket(bucket_name, std::to_string(new_creation_limit)); +} + +void Client::SetBotRequiredLevel(int new_required_level, uint8 class_id) +{ + const auto bucket_name = fmt::format( + "bot_required_level{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + SetBucket(bucket_name, std::to_string(new_required_level)); +} + +void Client::SetBotSpawnLimit(int new_spawn_limit, uint8 class_id) +{ + const auto bucket_name = fmt::format( + "bot_spawn_limit{}", + ( + class_id && IsPlayerClass(class_id) ? + fmt::format( + "_{}", + Strings::ToLower(GetClassIDName(class_id)) + ) : + "" + ) + ); + + SetBucket(bucket_name, std::to_string(new_spawn_limit)); +} + +#endif diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 4298625e7..87e9a2a22 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1065,11 +1065,21 @@ int Perl__createbotcount() return quest_manager.createbotcount(); } +int Perl__createbotcount(uint8 class_id) +{ + return quest_manager.createbotcount(class_id); +} + int Perl__spawnbotcount() { return quest_manager.spawnbotcount(); } +int Perl__spawnbotcount(uint8 class_id) +{ + return quest_manager.spawnbotcount(class_id); +} + bool Perl__botquest() { return quest_manager.botquest(); @@ -3847,8 +3857,10 @@ void perl_register_quest() #ifdef BOTS package.add("botquest", &Perl__botquest); - package.add("spawnbotcount", &Perl__spawnbotcount); - package.add("createbotcount", &Perl__createbotcount); + package.add("spawnbotcount", (int(*)())&Perl__spawnbotcount); + package.add("spawnbotcount", (int(*)(uint8))&Perl__spawnbotcount); + package.add("createbotcount", (int(*)())&Perl__createbotcount); + package.add("createbotcount", (int(*)(uint8))&Perl__createbotcount); package.add("createBot", &Perl__createBot); #endif //BOTS diff --git a/zone/entity.cpp b/zone/entity.cpp index ed0b73141..671d7660b 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5164,7 +5164,7 @@ void EntityList::GetBotList(std::list &b_list) } } -std::vector EntityList::GetBotListByCharacterID(uint32 character_id) +std::vector EntityList::GetBotListByCharacterID(uint32 character_id, uint8 class_id) { std::vector client_bot_list; @@ -5173,7 +5173,14 @@ std::vector EntityList::GetBotListByCharacterID(uint32 character_id) } for (auto bot : bot_list) { - if (bot->GetOwner() && bot->GetBotOwnerCharacterID() == character_id) { + if ( + bot->GetOwner() && + bot->GetBotOwnerCharacterID() == character_id && + ( + !class_id || + bot->GetClass() == class_id + ) + ) { client_bot_list.push_back(bot); } } diff --git a/zone/entity.h b/zone/entity.h index 65c1eb7e7..05551d634 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -535,7 +535,7 @@ public: inline const std::unordered_map &GetClientList() { return client_list; } #ifdef BOTS inline const std::list &GetBotList() { return bot_list; } - std::vector GetBotListByCharacterID(uint32 character_id); + std::vector GetBotListByCharacterID(uint32 character_id, uint8 class_id = 0); std::vector GetBotListByClientName(std::string client_name); #endif inline const std::unordered_map &GetCorpseList() { return corpse_list; } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 767acbbec..7278408c1 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -2613,6 +2613,82 @@ void Lua_Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in self->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); } +#ifdef BOTS + +int Lua_Client::GetBotRequiredLevel() +{ + Lua_Safe_Call_Int(); + return self->GetBotRequiredLevel(); +} + +int Lua_Client::GetBotRequiredLevel(uint8 class_id) +{ + Lua_Safe_Call_Int(); + return self->GetBotRequiredLevel(class_id); +} + +uint32 Lua_Client::GetBotCreationLimit() +{ + Lua_Safe_Call_Int(); + return self->GetBotCreationLimit(); +} + +uint32 Lua_Client::GetBotCreationLimit(uint8 class_id) +{ + Lua_Safe_Call_Int(); + return self->GetBotCreationLimit(class_id); +} + +int Lua_Client::GetBotSpawnLimit() +{ + Lua_Safe_Call_Int(); + return self->GetBotSpawnLimit(); +} + +int Lua_Client::GetBotSpawnLimit(uint8 class_id) +{ + Lua_Safe_Call_Int(); + return self->GetBotSpawnLimit(class_id); +} + +void Lua_Client::SetBotRequiredLevel(int new_required_level) +{ + Lua_Safe_Call_Void(); + self->SetBotRequiredLevel(new_required_level); +} + +void Lua_Client::SetBotRequiredLevel(int new_required_level, uint8 class_id) +{ + Lua_Safe_Call_Void(); + self->SetBotRequiredLevel(new_required_level, class_id); +} + +void Lua_Client::SetBotCreationLimit(uint32 new_creation_limit) +{ + Lua_Safe_Call_Void(); + self->SetBotCreationLimit(new_creation_limit); +} + +void Lua_Client::SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id) +{ + Lua_Safe_Call_Void(); + self->SetBotCreationLimit(new_creation_limit, class_id); +} + +void Lua_Client::SetBotSpawnLimit(int new_spawn_limit) +{ + Lua_Safe_Call_Void(); + self->SetBotSpawnLimit(new_spawn_limit); +} + +void Lua_Client::SetBotSpawnLimit(int new_spawn_limit, uint8 class_id) +{ + Lua_Safe_Call_Void(); + self->SetBotSpawnLimit(new_spawn_limit, class_id); +} + +#endif + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2733,6 +2809,18 @@ luabind::scope lua_register_client() { .def("GetBindZ", (float(Lua_Client::*)(void))&Lua_Client::GetBindZ) .def("GetBindZoneID", (uint32(Lua_Client::*)(int))&Lua_Client::GetBindZoneID) .def("GetBindZoneID", (uint32(Lua_Client::*)(void))&Lua_Client::GetBindZoneID) + +#ifdef BOTS + + .def("GetBotCreationLimit", (uint32(Lua_Client::*)(void))&Lua_Client::GetBotCreationLimit) + .def("GetBotCreationLimit", (uint32(Lua_Client::*)(uint8))&Lua_Client::GetBotCreationLimit) + .def("GetBotRequiredLevel", (int(Lua_Client::*)(void))&Lua_Client::GetBotRequiredLevel) + .def("GetBotRequiredLevel", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotRequiredLevel) + .def("GetBotSpawnLimit", (int(Lua_Client::*)(void))&Lua_Client::GetBotSpawnLimit) + .def("GetBotSpawnLimit", (int(Lua_Client::*)(uint8))&Lua_Client::GetBotSpawnLimit) + +#endif + .def("GetCarriedMoney", (uint64(Lua_Client::*)(void))&Lua_Client::GetCarriedMoney) .def("GetCarriedPlatinum", (uint32(Lua_Client::*)(void))&Lua_Client::GetCarriedPlatinum) .def("GetCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetCharacterFactionLevel) @@ -2968,6 +3056,18 @@ luabind::scope lua_register_client() { .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float,float))&Lua_Client::SetBindPoint) .def("SetBindPoint", (void(Lua_Client::*)(int,int,float,float,float,float))&Lua_Client::SetBindPoint) .def("SetBindPoint", (void(Lua_Client::*)(void))&Lua_Client::SetBindPoint) + +#ifdef BOTS + + .def("SetBotCreationLimit", (void(Lua_Client::*)(uint32))&Lua_Client::SetBotCreationLimit) + .def("SetBotCreationLimit", (void(Lua_Client::*)(uint32,uint8))&Lua_Client::SetBotCreationLimit) + .def("SetBotRequiredLevel", (void(Lua_Client::*)(int))&Lua_Client::SetBotRequiredLevel) + .def("SetBotRequiredLevel", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotRequiredLevel) + .def("SetBotSpawnLimit", (void(Lua_Client::*)(int))&Lua_Client::SetBotSpawnLimit) + .def("SetBotSpawnLimit", (void(Lua_Client::*)(int,uint8))&Lua_Client::SetBotSpawnLimit) + +#endif + .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) .def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption) .def("SetDeity", (void(Lua_Client::*)(int))&Lua_Client::SetDeity) diff --git a/zone/lua_client.h b/zone/lua_client.h index f0a0a346c..364d8bd7b 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -451,6 +451,23 @@ public: bool SendGMCommand(std::string message); bool SendGMCommand(std::string message, bool ignore_status); +#ifdef BOTS + + int GetBotRequiredLevel(); + int GetBotRequiredLevel(uint8 class_id); + uint32 GetBotCreationLimit(); + uint32 GetBotCreationLimit(uint8 class_id); + int GetBotSpawnLimit(); + int GetBotSpawnLimit(uint8 class_id); + void SetBotRequiredLevel(int new_required_level); + void SetBotRequiredLevel(int new_required_level, uint8 class_id); + void SetBotCreationLimit(uint32 new_creation_limit); + void SetBotCreationLimit(uint32 new_creation_limit, uint8 class_id); + void SetBotSpawnLimit(int new_spawn_limit); + void SetBotSpawnLimit(int new_spawn_limit, uint8 class_id); + +#endif + void DialogueWindow(std::string markdown); Lua_Expedition CreateExpedition(luabind::object expedition_info); diff --git a/zone/lua_entity_list.cpp b/zone/lua_entity_list.cpp index f8b237a01..5c6f7a2f3 100644 --- a/zone/lua_entity_list.cpp +++ b/zone/lua_entity_list.cpp @@ -413,6 +413,20 @@ Lua_Bot_List Lua_EntityList::GetBotListByCharacterID(uint32 character_id) { return ret; } +Lua_Bot_List Lua_EntityList::GetBotListByCharacterID(uint32 character_id, uint8 class_id) { + Lua_Safe_Call_Class(Lua_Bot_List); + Lua_Bot_List ret; + auto bot_list = self->GetBotListByCharacterID(character_id, class_id); + + if (bot_list.size()) { + for (auto bot : bot_list) { + ret.entries.push_back(Lua_Bot(bot)); + } + } + + return ret; +} + Lua_Bot_List Lua_EntityList::GetBotListByClientName(std::string client_name) { Lua_Safe_Call_Class(Lua_Bot_List); Lua_Bot_List ret; @@ -607,6 +621,7 @@ luabind::scope lua_register_entity_list() { .def("GetBotByName", (Lua_Bot(Lua_EntityList::*)(std::string))&Lua_EntityList::GetBotByName) .def("GetBotList", (Lua_Bot_List(Lua_EntityList::*)(void))&Lua_EntityList::GetBotList) .def("GetBotListByCharacterID", (Lua_Bot_List(Lua_EntityList::*)(uint32))&Lua_EntityList::GetBotListByCharacterID) + .def("GetBotListByCharacterID", (Lua_Bot_List(Lua_EntityList::*)(uint32,uint8))&Lua_EntityList::GetBotListByCharacterID) .def("GetBotListByClientName", (Lua_Bot_List(Lua_EntityList::*)(std::string))&Lua_EntityList::GetBotListByClientName) #endif .def("GetClientByAccID", (Lua_Client(Lua_EntityList::*)(uint32))&Lua_EntityList::GetClientByAccID) diff --git a/zone/lua_entity_list.h b/zone/lua_entity_list.h index 2c8d1dc11..e8c4ac53b 100644 --- a/zone/lua_entity_list.h +++ b/zone/lua_entity_list.h @@ -135,6 +135,7 @@ public: Lua_Bot GetBotByName(std::string bot_name); Lua_Bot_List GetBotList(); Lua_Bot_List GetBotListByCharacterID(uint32 character_id); + Lua_Bot_List GetBotListByCharacterID(uint32 character_id, uint8 class_id); Lua_Bot_List GetBotListByClientName(std::string client_name); Lua_Bot GetRandomBot(); Lua_Bot GetRandomBot(float x, float y, float z, float distance); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index c292a6352..f02ffae4b 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -2507,6 +2507,70 @@ void Perl_Client_SendMarqueeMessage(Client* self, uint32 type, uint32 priority, self->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message); } +#ifdef BOTS + +int Perl_Client_GetBotRequiredLevel(Client* self) +{ + return self->GetBotRequiredLevel(); +} + +int Perl_Client_GetBotRequiredLevel(Client* self, uint8 class_id) +{ + return self->GetBotRequiredLevel(class_id); +} + +uint32 Perl_Client_GetBotCreationLimit(Client* self) +{ + return self->GetBotCreationLimit(); +} + +uint32 Perl_Client_GetBotCreationLimit(Client* self, uint8 class_id) +{ + return self->GetBotCreationLimit(class_id); +} + +int Perl_Client_GetBotSpawnLimit(Client* self) +{ + return self->GetBotSpawnLimit(); +} + +int Perl_Client_GetBotSpawnLimit(Client* self, uint8 class_id) +{ + return self->GetBotSpawnLimit(class_id); +} + +void Perl_Client_SetBotRequiredLevel(Client* self, int new_required_level) +{ + self->SetBotRequiredLevel(new_required_level); +} + +void Perl_Client_SetBotRequiredLevel(Client* self, int new_required_level, uint8 class_id) +{ + self->SetBotRequiredLevel(new_required_level, class_id); +} + +void Perl_Client_SetBotCreationLimit(Client* self, uint32 new_creation_limit) +{ + self->SetBotCreationLimit(new_creation_limit); +} + +void Perl_Client_SetBotCreationLimit(Client* self, uint32 new_creation_limit, uint8 class_id) +{ + self->SetBotCreationLimit(new_creation_limit, class_id); +} + +void Perl_Client_SetBotSpawnLimit(Client* self, int new_spawn_limit) +{ + self->SetBotSpawnLimit(new_spawn_limit); +} + +void Perl_Client_SetBotSpawnLimit(Client* self, int new_spawn_limit, uint8 class_id) +{ + self->SetBotSpawnLimit(new_spawn_limit, class_id); +} + +#endif + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -2626,6 +2690,18 @@ void perl_register_client() package.add("GetBindZ", (float(*)(Client*, int))&Perl_Client_GetBindZ); package.add("GetBindZoneID", (uint32_t(*)(Client*))&Perl_Client_GetBindZoneID); package.add("GetBindZoneID", (uint32_t(*)(Client*, int))&Perl_Client_GetBindZoneID); + +#ifdef BOTS + + package.add("GetBotCreationLimit", (uint32(*)(Client*))&Perl_Client_GetBotCreationLimit); + package.add("GetBotCreationLimit", (uint32(*)(Client*, uint8))&Perl_Client_GetBotCreationLimit); + package.add("GetBotRequiredLevel", (int(*)(Client*))&Perl_Client_GetBotRequiredLevel); + package.add("GetBotRequiredLevel", (int(*)(Client*, uint8))&Perl_Client_GetBotRequiredLevel); + package.add("GetBotSpawnLimit", (int(*)(Client*))&Perl_Client_GetBotSpawnLimit); + package.add("GetBotSpawnLimit", (int(*)(Client*, uint8))&Perl_Client_GetBotSpawnLimit); + +#endif + package.add("GetCarriedMoney", &Perl_Client_GetCarriedMoney); package.add("GetCarriedPlatinum", &Perl_Client_GetCarriedPlatinum); package.add("GetCharacterFactionLevel", &Perl_Client_GetCharacterFactionLevel); @@ -2863,6 +2939,18 @@ void perl_register_client() package.add("SetBindPoint", (void(*)(Client*, int, int, float, float))&Perl_Client_SetBindPoint); package.add("SetBindPoint", (void(*)(Client*, int, int, float, float, float))&Perl_Client_SetBindPoint); package.add("SetBindPoint", (void(*)(Client*, int, int, float, float, float, float))&Perl_Client_SetBindPoint); + +#ifdef BOTS + + package.add("SetBotCreationLimit", (void(*)(Client*, uint32))&Perl_Client_SetBotCreationLimit); + package.add("SetBotCreationLimit", (void(*)(Client*, uint32, uint8))&Perl_Client_SetBotCreationLimit); + package.add("SetBotRequiredLevel", (void(*)(Client*, int))&Perl_Client_SetBotRequiredLevel); + package.add("SetBotRequiredLevel", (void(*)(Client*, int, uint8))&Perl_Client_SetBotRequiredLevel); + package.add("SetBotSpawnLimit", (void(*)(Client*, int))&Perl_Client_SetBotSpawnLimit); + package.add("SetBotSpawnLimit", (void(*)(Client*, int, uint8))&Perl_Client_SetBotSpawnLimit); + +#endif + package.add("SetClientMaxLevel", &Perl_Client_SetClientMaxLevel); package.add("SetConsumption", &Perl_Client_SetConsumption); package.add("SetCustomItemData", &Perl_Client_SetCustomItemData); diff --git a/zone/perl_entity.cpp b/zone/perl_entity.cpp index 94b9119cb..8db8b7075 100644 --- a/zone/perl_entity.cpp +++ b/zone/perl_entity.cpp @@ -428,6 +428,17 @@ perl::array Perl_EntityList_GetBotListByCharacterID(EntityList* self, uint32_t c return result; } +perl::array Perl_EntityList_GetBotListByCharacterID(EntityList* self, uint32_t character_id, uint8_t class_id) // @categories Script Utility, Bot +{ + perl::array result; + auto current_bot_list = self->GetBotListByCharacterID(character_id, class_id); + for (int i = 0; i < current_bot_list.size(); ++i) + { + result.push_back(current_bot_list[i]); + } + return result; +} + perl::array Perl_EntityList_GetBotListByClientName(EntityList* self, std::string client_name) // @categories Script Utility, Bot { perl::array result; @@ -581,7 +592,8 @@ void perl_register_entitylist() package.add("GetBotByID", &Perl_EntityList_GetBotByID); package.add("GetBotByName", &Perl_EntityList_GetBotByName); package.add("GetBotList", &Perl_EntityList_GetBotList); - package.add("GetBotListByCharacterID", &Perl_EntityList_GetBotListByCharacterID); + package.add("GetBotListByCharacterID", (perl::array(*)(EntityList*, uint32))&Perl_EntityList_GetBotListByCharacterID); + package.add("GetBotListByCharacterID", (perl::array(*)(EntityList*, uint32, uint8))&Perl_EntityList_GetBotListByCharacterID); package.add("GetBotListByClientName", &Perl_EntityList_GetBotListByClientName); #endif package.add("GetClientByAccID", &Perl_EntityList_GetClientByAccID); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 55e7dd085..605a32d5b 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2212,11 +2212,21 @@ void QuestManager::popup(const char *title, const char *text, uint32 popupid, ui #ifdef BOTS -int QuestManager::createbotcount() { +int QuestManager::createbotcount(uint8 class_id) { + QuestManagerCurrentQuestVars(); + if (initiator) { + return initiator->GetBotCreationLimit(class_id); + } + return RuleI(Bots, CreationLimit); } -int QuestManager::spawnbotcount() { +int QuestManager::spawnbotcount(uint8 class_id) { + QuestManagerCurrentQuestVars(); + if (initiator) { + return initiator->GetBotSpawnLimit(class_id); + } + return RuleI(Bots, SpawnLimit); } @@ -2228,49 +2238,167 @@ bool QuestManager::botquest() bool QuestManager::createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender) { QuestManagerCurrentQuestVars(); - uint32 MaxBotCreate = RuleI(Bots, CreationLimit); - if (initiator) - { - if(Bot::SpawnedBotCount(initiator->CharacterID()) >= MaxBotCreate) - { - initiator->Message(Chat::Yellow,"You have the maximum number of bots allowed."); + if (initiator) { + auto bot_creation_limit = initiator->GetBotCreationLimit(); + auto bot_creation_limit_class = initiator->GetBotCreationLimit(botclass); + auto bot_spawn_limit = initiator->GetBotSpawnLimit(); + auto bot_spawn_limit_class = initiator->GetBotSpawnLimit(botclass); + + uint32 bot_count = 0; + uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(initiator->CharacterID(), botclass, bot_count, bot_class_count)) { + initiator->Message(Chat::White, "Failed to query bot count."); + return false; + } + + if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { + std::string message; + + if (bot_creation_limit) { + message = fmt::format( + "You cannot create anymore than {} bot{}.", + bot_creation_limit, + bot_creation_limit != 1 ? "s" : "" + ); + } else { + message = "You cannot create any bots."; + } + + initiator->Message(Chat::White, message.c_str()); + return false; + } + + if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { + std::string message; + + if (bot_creation_limit_class) { + message = fmt::format( + "You cannot create anymore than {} {} bot{}.", + bot_creation_limit_class, + GetClassIDName(botclass), + bot_creation_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You cannot create any {} bots.", + GetClassIDName(botclass) + ); + } + + initiator->Message(Chat::White, message.c_str()); + return false; + } + + auto spawned_bot_count = Bot::SpawnedBotCount(initiator->CharacterID()); + + if ( + bot_spawn_limit >= 0 && + spawned_bot_count >= bot_spawn_limit && + !initiator->GetGM() + ) { + std::string message; + if (bot_spawn_limit) { + message = fmt::format( + "You cannot have more than {} spawned bot{}.", + bot_spawn_limit, + bot_spawn_limit != 1 ? "s" : "" + ); + } else { + message = "You are not currently allowed to spawn any bots."; + } + + initiator->Message(Chat::White, message.c_str()); + return false; + } + + auto spawned_bot_count_class = Bot::SpawnedBotCount(initiator->CharacterID(), botclass); + + if ( + bot_spawn_limit_class >= 0 && + spawned_bot_count_class >= bot_spawn_limit_class && + !initiator->GetGM() + ) { + std::string message; + if (bot_spawn_limit_class) { + message = fmt::format( + "You cannot have more than {} spawned {} bot{}.", + bot_spawn_limit_class, + GetClassIDName(botclass), + bot_spawn_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You are not currently allowed to spawn any {} bots.", + GetClassIDName(botclass) + ); + } + + initiator->Message(Chat::White, message.c_str()); return false; } std::string test_name = name; bool available_flag = false; - if(!database.botdb.QueryNameAvailablity(test_name, available_flag)) { - initiator->Message(Chat::White, "%s for '%s'", BotDatabase::fail::QueryNameAvailablity(), (char*)name); + if (!database.botdb.QueryNameAvailablity(test_name, available_flag)) { + initiator->Message( + Chat::White, + fmt::format( + "Failed to query name availability for '{}'.", + test_name + ).c_str() + ); return false; } + if (!available_flag) { - initiator->Message(Chat::White, "The name %s is already being used or is invalid. Please choose a different name.", (char*)name); + initiator->Message( + Chat::White, + fmt::format( + "The name {} is already being used or is invalid. Please choose a different name.", + test_name + ).c_str() + ); return false; } - Bot* NewBot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(name, lastname, level, race, botclass, gender), initiator); + Bot* new_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(name, lastname, level, race, botclass, gender), initiator); - if(NewBot) - { - if(!NewBot->IsValidRaceClassCombo()) { + if (new_bot) { + if (!new_bot->IsValidRaceClassCombo()) { initiator->Message(Chat::White, "That Race/Class combination cannot be created."); return false; } - if(!NewBot->IsValidName()) { - initiator->Message(Chat::White, "%s has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name.", NewBot->GetCleanName()); + if (!new_bot->IsValidName()) { + initiator->Message( + Chat::White, + fmt::format( + "{} has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name.", + new_bot->GetCleanName() + ).c_str() + ); return false; } // Now that all validation is complete, we can save our newly created bot - if(!NewBot->Save()) - { - initiator->Message(Chat::White, "Unable to save %s as a bot.", NewBot->GetCleanName()); - } - else - { - initiator->Message(Chat::White, "%s saved as bot %u.", NewBot->GetCleanName(), NewBot->GetBotID()); + if (!new_bot->Save()) { + initiator->Message( + Chat::White, + fmt::format( + "Unable to save {} as a bot.", + new_bot->GetCleanName() + ).c_str() + ); + } else { + initiator->Message( + Chat::White, + fmt::format( + "{} saved as bot ID {}.", + new_bot->GetCleanName(), + new_bot->GetBotID() + ).c_str() + ); return true; } } diff --git a/zone/questmgr.h b/zone/questmgr.h index aeb4320d3..845781546 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -355,8 +355,8 @@ public: inline bool ProximitySayInUse() { return HaveProximitySays; } #ifdef BOTS - int createbotcount(); - int spawnbotcount(); + int createbotcount(uint8 class_id = 0); + int spawnbotcount(uint8 class_id = 0); bool botquest(); bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender); #endif