diff --git a/common/version.h b/common/version.h index 0c6dea2b5..5640a63fe 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9142 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9025 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9026 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index 042c3d078..e3a051ef7 100644 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -399,11 +399,13 @@ sub build_linux_source { print `git clone https://github.com/EQEmu/Server.git`; mkdir($source_dir . "/Server/build") if (!-e $source_dir . "/Server/build"); - chdir($source_dir . "/Server/build"); + chdir($source_dir . "/Server"); print `git submodule init`; print `git submodule update`; + chdir($source_dir . "/Server/build"); + print "Generating CMake build files...\n"; if ($os_flavor eq "fedora_core") { print `cmake $cmake_options -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_LUA=ON -DLUA_INCLUDE_DIR=/usr/include/lua-5.1/ -G "Unix Makefiles" ..`; diff --git a/utils/scripts/linux_installer/install.sh b/utils/scripts/linux_installer/install.sh index 6af4a9df3..5cef4ab8b 100644 --- a/utils/scripts/linux_installer/install.sh +++ b/utils/scripts/linux_installer/install.sh @@ -153,7 +153,7 @@ elif [[ "$OS" == "red_hat" ]]; then yum -y install \ open-vm-tools \ vim \ - cmake \ + cmake3 \ boost-* \ zlib-devel \ mariadb \ @@ -178,6 +178,12 @@ elif [[ "$OS" == "red_hat" ]]; then "Development Tools" \ "Basic Web Server" \ "Compatibility Libraries" + # Deal with the cmake 3 prerequisite on RHEL/CentOS 6/7 Note: Might break with RHEL/CentOS 8 + alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake3 20 \ + --slave /usr/local/bin/ctest ctest /usr/bin/ctest3 \ + --slave /usr/local/bin/cpack cpack /usr/bin/cpack3 \ + --slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake3 \ + --family cmake elif [[ "$OS" == "fedora_core" ]]; then # Do Fedora stuff diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 405cf3f5c..0a1e4466a 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -24,6 +24,7 @@ 9023|2019_06_22_bots_owner_option_stats_update.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'stats_update'|empty| 9024|2019_06_27_bots_pet_get_lost.sql|SELECT `bot_command` FROM `bot_command_settings` WHERE `bot_command` LIKE 'petgetlost'|empty| 9025|2019_08_26_bots_owner_option_spawn_message.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'spawn_message_enabled'|empty| +9026|2019_09_09_bots_owner_options_rework.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'option_type'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2019_09_09_bots_owner_options_rework.sql b/utils/sql/git/bots/required/2019_09_09_bots_owner_options_rework.sql new file mode 100644 index 000000000..6240e9366 --- /dev/null +++ b/utils/sql/git/bots/required/2019_09_09_bots_owner_options_rework.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `bot_owner_options`; + +CREATE TABLE `bot_owner_options` ( + `owner_id` INT(11) UNSIGNED NOT NULL, + `option_type` SMALLINT(3) UNSIGNED NOT NULL, + `option_value` SMALLINT(3) UNSIGNED NULL DEFAULT '0', + PRIMARY KEY (`owner_id`, `option_type`) +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; diff --git a/zone/bot.cpp b/zone/bot.cpp index 971c1d1b6..efc5510b8 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -255,6 +255,18 @@ void Bot::SetBotSpellID(uint32 newSpellID) { this->npc_spells_id = newSpellID; } +void Bot::SetSurname(std::string bot_surname) { + _surname = bot_surname.substr(0, 31); +} + +void Bot::SetTitle(std::string bot_title) { + _title = bot_title.substr(0, 31); +} + +void Bot::SetSuffix(std::string bot_suffix) { + _suffix = bot_suffix.substr(0, 31); +} + uint32 Bot::GetBotArcheryRange() { const EQEmu::ItemInstance *range_inst = GetBotItem(EQEmu::invslot::slotRange); const EQEmu::ItemInstance *ammo_inst = GetBotItem(EQEmu::invslot::slotAmmo); @@ -3260,7 +3272,7 @@ bool Bot::Spawn(Client* botCharacterOwner) { else this->GetBotOwner()->CastToClient()->Message(Chat::Red, "%s save failed!", this->GetCleanName()); - // Spawn the bot at the bow owner's loc + // Spawn the bot at the bot owner's loc this->m_Position.x = botCharacterOwner->GetX(); this->m_Position.y = botCharacterOwner->GetY(); this->m_Position.z = botCharacterOwner->GetZ(); @@ -3365,6 +3377,9 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { ns->spawn.helm = helmtexture; //(GetShowHelm() ? helmtexture : 0); //0xFF; ns->spawn.equip_chest2 = texture; //0xFF; ns->spawn.show_name = true; + strcpy(ns->spawn.lastName, GetSurname().c_str()); + strcpy(ns->spawn.title, GetTitle().c_str()); + strcpy(ns->spawn.suffix, GetSuffix().c_str()); const EQEmu::ItemData* item = nullptr; const EQEmu::ItemInstance* inst = nullptr; uint32 spawnedbotid = 0; @@ -3499,7 +3514,7 @@ void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) { Bot* bot = *biter; if(bot && (bot->GetLevel() != client->GetLevel())) { bot->SetPetChooser(false); // not sure what this does, but was in bot 'update' code - bot->CalcBotStats(client->GetBotOptionStatsUpdate()); + bot->CalcBotStats(client->GetBotOption(Client::booStatsUpdate)); if(sendlvlapp) bot->SendLevelAppearance(); // modified from Client::SetLevel() @@ -4178,7 +4193,7 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli client->Message(Chat::Lime, "Trade with '%s' resulted in %i accepted item%s, %i returned item%s.", GetCleanName(), accepted_count, ((accepted_count == 1) ? "" : "s"), returned_count, ((returned_count == 1) ? "" : "s")); if (accepted_count) - CalcBotStats(client->GetBotOptionStatsUpdate()); + CalcBotStats(client->GetBotOption(Client::booStatsUpdate)); } bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { @@ -4188,7 +4203,7 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk Save(); Mob *my_owner = GetBotOwner(); - if (my_owner && my_owner->IsClient() && my_owner->CastToClient()->GetBotOptionDeathMarquee()) { + if (my_owner && my_owner->IsClient() && my_owner->CastToClient()->GetBotOption(Client::booDeathMarquee)) { if (killerMob) my_owner->CastToClient()->SendMarqueeMessage(Chat::Yellow, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName())); else diff --git a/zone/bot.h b/zone/bot.h index 3b363748a..20348181e 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -270,7 +270,12 @@ public: bool GetNeedsHateRedux(Mob *tar); bool HasOrMayGetAggro(); void SetDefaultBotStance(); - + void SetSurname(std::string bot_surname); + void SetTitle(std::string bot_title); + void SetSuffix(std::string bot_suffix); + std::string GetSurname() { return _surname; } + std::string GetTitle() { return _title; } + std::string GetSuffix() { return _suffix; } inline virtual int32 GetMaxStat(); inline virtual int32 GetMaxResist(); inline virtual int32 GetMaxSTR(); @@ -650,6 +655,9 @@ private: uint32 _guildId; uint8 _guildRank; std::string _guildName; + std::string _surname; + std::string _title; + std::string _suffix; uint32 _lastZoneId; bool _rangerAutoWeaponSelect; BotRoleType _botRole; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 391f55dd7..467996848 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1351,10 +1351,13 @@ int bot_command_init(void) bot_command_add("botspawn", "Spawns a created bot", 0, bot_subcommand_bot_spawn) || bot_command_add("botstance", "Changes the stance of a bot", 0, bot_subcommand_bot_stance) || bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", 0, bot_subcommand_bot_stop_melee_level) || + bot_command_add("botsuffix", "Sets a bots suffix", 0, bot_subcommand_bot_suffix) || bot_command_add("botsummon", "Summons bot(s) to your location", 0, bot_subcommand_bot_summon) || + bot_command_add("botsurname", "Sets a bots surname (last name)", 0, bot_subcommand_bot_surname) || bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", 0, bot_subcommand_bot_tattoo) || bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", 0, bot_subcommand_bot_toggle_archer) || bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", 0, bot_subcommand_bot_toggle_helm) || + bot_command_add("bottitle", "Sets a bots title", 0, bot_subcommand_bot_title) || bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", 0, bot_subcommand_bot_update) || bot_command_add("botwoad", "Changes the Barbarian woad of a bot", 0, bot_subcommand_bot_woad) || bot_command_add("charm", "Attempts to have a bot charm your target", 0, bot_command_charm) || @@ -3507,63 +3510,180 @@ void bot_command_movement_speed(Client *c, const Seperator *sep) void bot_command_owner_option(Client *c, const Seperator *sep) { if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(m_usage, "usage: %s [deathmarquee | statsupdate] (argument: enable | disable | null (toggles))", sep->arg[0]); - c->Message(m_usage, "usage: %s [spawnmessage] [argument: say | tell | silent | class | default]", sep->arg[0]); + + c->Message(m_usage, "usage: %s [option] [argument | null]", sep->arg[0]); + + std::string window_title = "Bot Owner Options"; + std::string window_text = + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
OptionArgumentNotes
deathmarqueeenable
disable
null(toggles)
statsupdateenable
disable
null(toggles)
spawnmessagesay
tell
silent
class
default
"; + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + return; } - std::string owner_option = sep->arg[1]; - std::string argument = sep->arg[2]; + std::string owner_option(sep->arg[1]); + std::string argument(sep->arg[2]); if (!owner_option.compare("deathmarquee")) { - if (!argument.compare("enable")) - c->SetBotOptionDeathMarquee(true); - else if (!argument.compare("disable")) - c->SetBotOptionDeathMarquee(false); - else - c->SetBotOptionDeathMarquee(!c->GetBotOptionDeathMarquee()); - - database.botdb.SaveOwnerOptionDeathMarquee(c->CharacterID(), c->GetBotOptionDeathMarquee()); - c->Message(m_action, "Bot 'death marquee' is now %s.", (c->GetBotOptionDeathMarquee() == true ? "enabled" : "disabled")); - } - else if (!owner_option.compare("statsupdate")) { - if (!argument.compare("enable")) - c->SetBotOptionStatsUpdate(true); - else if (!argument.compare("disable")) - c->SetBotOptionStatsUpdate(false); - else - c->SetBotOptionStatsUpdate(!c->GetBotOptionStatsUpdate()); - database.botdb.SaveOwnerOptionStatsUpdate(c->CharacterID(), c->GetBotOptionStatsUpdate()); - c->Message(m_action, "Bot 'stats update' is now %s.", (c->GetBotOptionStatsUpdate() == true ? "enabled" : "disabled")); - } - else if (!owner_option.compare("spawnmessage")) { - if (!argument.compare("say")) { - c->SetBotOptionSpawnMessageSay(); + if (!argument.compare("enable")) { + c->SetBotOption(Client::booDeathMarquee, true); } - else if (!argument.compare("tell")) { - c->SetBotOptionSpawnMessageTell(); - } - else if (!argument.compare("silent")) { - c->SetBotOptionSpawnMessageSilent(); - } - else if (!argument.compare("class")) { - c->SetBotOptionSpawnMessageClassSpecific(true); - } - else if (!argument.compare("default")) { - c->SetBotOptionSpawnMessageClassSpecific(false); + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booDeathMarquee, false); } else { + c->SetBotOption(Client::booDeathMarquee, !c->GetBotOption(Client::booDeathMarquee)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booDeathMarquee, c->GetBotOption(Client::booDeathMarquee)); + + c->Message(m_action, "Bot 'death marquee' is now %s.", (c->GetBotOption(Client::booDeathMarquee) == true ? "enabled" : "disabled")); + } + else if (!owner_option.compare("statsupdate")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booStatsUpdate, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booStatsUpdate, false); + } + else { + c->SetBotOption(Client::booStatsUpdate, !c->GetBotOption(Client::booStatsUpdate)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booStatsUpdate, c->GetBotOption(Client::booStatsUpdate)); + + c->Message(m_action, "Bot 'stats update' is now %s.", (c->GetBotOption(Client::booStatsUpdate) == true ? "enabled" : "disabled")); + } + else if (!owner_option.compare("spawnmessage")) { + + Client::BotOwnerOption boo = Client::_booCount; + + if (!argument.compare("say")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, true); + c->SetBotOption(Client::booSpawnMessageTell, false); + } + else if (!argument.compare("tell")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, false); + c->SetBotOption(Client::booSpawnMessageTell, true); + } + else if (!argument.compare("silent")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, false); + c->SetBotOption(Client::booSpawnMessageTell, false); + } + else if (!argument.compare("class")) { + + boo = Client::booSpawnMessageClassSpecific; + c->SetBotOption(Client::booSpawnMessageClassSpecific, true); + } + else if (!argument.compare("default")) { + + boo = Client::booSpawnMessageClassSpecific; + c->SetBotOption(Client::booSpawnMessageClassSpecific, false); + } + else { + c->Message(m_fail, "Owner option '%s' argument '%s' is not recognized.", owner_option.c_str(), argument.c_str()); return; } - database.botdb.SaveOwnerOptionSpawnMessage( - c->CharacterID(), - c->GetBotOptionSpawnMessageSay(), - c->GetBotOptionSpawnMessageTell(), - c->GetBotOptionSpawnMessageClassSpecific() - ); + if (boo == Client::booSpawnMessageSay) { + + database.botdb.SaveOwnerOption( + c->CharacterID(), + std::pair( + Client::booSpawnMessageSay, + Client::booSpawnMessageTell + ), + std::pair( + c->GetBotOption(Client::booSpawnMessageSay), + c->GetBotOption(Client::booSpawnMessageTell) + ) + ); + } + else if (boo == Client::booSpawnMessageClassSpecific) { + + database.botdb.SaveOwnerOption( + c->CharacterID(), + Client::booSpawnMessageClassSpecific, + c->GetBotOption(Client::booSpawnMessageClassSpecific) + ); + } + else { + + c->Message(m_action, "Bot 'spawn message' is now ERROR."); + return; + } + c->Message(m_action, "Bot 'spawn message' is now %s.", argument.c_str()); } else { @@ -4472,7 +4592,7 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) return; } std::string bot_name = sep->arg[1]; - + bot_name = ucfirst(bot_name); if (sep->arg[2][0] == '\0' || !sep->IsNumber(2)) { c->Message(m_fail, "Invalid Class!"); return; @@ -5000,17 +5120,22 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) if (helper_command_alias_fail(c, "bot_subcommand_bot_list", sep->arg[0], "botlist")) return; if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(m_usage, "usage: %s ([class] [value]) ([race] [value]) ([name] [partial-full])", sep->arg[0]); + c->Message(m_usage, "usage: %s (account) ([class] [value]) ([race] [value]) ([name] [partial-full])", sep->arg[0]); c->Message(m_note, "note: filter criteria is orderless and optional"); return; } - + bool Account = false; + int seps = 1; uint32 filter_value[FilterCount]; int name_criteria_arg = 0; memset(&filter_value, 0, sizeof(uint32) * FilterCount); int filter_mask = 0; - for (int i = 1; i < (FilterCount * 2); i += 2) { + if (strcasecmp(sep->arg[1], "account") == 0) { + Account = true; + seps = 2; + } + for (int i = seps; i < (FilterCount * 2); i += 2) { if (sep->arg[i][0] == '\0') break; @@ -5035,7 +5160,7 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) } std::list bots_list; - if (!database.botdb.LoadBotsList(c->CharacterID(), bots_list)) { + if (!database.botdb.LoadBotsList(c->CharacterID(), bots_list, Account)) { c->Message(m_fail, "%s", BotDatabase::fail::LoadBotsList()); return; } @@ -5045,6 +5170,7 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) } int bot_count = 0; + int bots_owned = 0; for (auto bots_iter : bots_list) { if (filter_mask) { if ((filter_mask & MaskClass) && filter_value[FilterClass] != bots_iter.Class) @@ -5060,23 +5186,26 @@ void bot_subcommand_bot_list(Client *c, const Seperator *sep) continue; } } - - c->Message(m_message, "%s is a level %u %s %s %s", - bots_iter.Name, + Bot * botCheckNotOnline = entity_list.GetBotByBotName(bots_iter.Name); + std::string botspawn_saylink = StringFormat("^botspawn %s", bots_iter.Name); + c->Message(Chat::White, "%s is a level %u %s %s %s who is owned by %s", + ((c->CharacterID() == bots_iter.Owner_ID) && (!botCheckNotOnline) ? (EQEmu::SayLinkEngine::GenerateQuestSaylink(botspawn_saylink, false, bots_iter.Name).c_str()) : (bots_iter.Name)), bots_iter.Level, Bot::RaceIdToString(bots_iter.Race).c_str(), ((bots_iter.Gender == FEMALE) ? ("Female") : ((bots_iter.Gender == MALE) ? ("Male") : ("Neuter"))), - Bot::ClassIdToString(bots_iter.Class).c_str() + Bot::ClassIdToString(bots_iter.Class).c_str(), + bots_iter.Owner ); - + if (c->CharacterID() == bots_iter.Owner_ID) { ++bots_owned; } ++bot_count; } if (!bot_count) { - c->Message(m_fail, "You have no bots meeting this criteria"); + c->Message(Chat::Red, "You have no bots meeting this criteria"); } else { - c->Message(m_action, "%i of %i bot%s shown", bot_count, bots_list.size(), ((bot_count != 1) ? ("s") : (""))); - c->Message(m_message, "Your limit is %i bot%s", RuleI(Bots, CreationLimit), ((RuleI(Bots, CreationLimit) != 1) ? ("s") : (""))); + c->Message(Chat::Yellow, "%i of %i bot%s shown.", bot_count, bots_list.size(), ((bot_count != 1) ? ("s") : (""))); + c->Message(Chat::Yellow, "%i of %i bot%s are owned by you. (You may spawn any available by clicking name)", bots_owned, bot_count, ((bot_count != 1) ? ("s") : (""))); + c->Message(Chat::White, "Your limit is %i bot%s", RuleI(Bots, CreationLimit), ((RuleI(Bots, CreationLimit) != 1) ? ("s") : (""))); } } @@ -5122,6 +5251,103 @@ void bot_subcommand_bot_out_of_combat(Client *c, const Seperator *sep) } } +void bot_subcommand_bot_surname(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::Red, "You must specify a [surname] to use this command (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::Red, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::Red, "Surname must be 31 characters or less."); + return; + } + std::string bot_surname = sep->arg[1]; + bot_surname = (bot_surname == "-remove") ? "" : bot_surname; + std::replace(bot_surname.begin(), bot_surname.end(), '_', ' '); + my_bot->SetSurname(bot_surname); + if (!database.botdb.SaveBot(my_bot)) { + c->Message(Chat::Red, BotDatabase::fail::SaveBot()); + return; + } + else { + auto outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); + GMLastName_Struct * gmn = (GMLastName_Struct*)outapp->pBuffer; + strcpy(gmn->name, my_bot->GetCleanName()); + strcpy(gmn->gmname, my_bot->GetCleanName()); + strcpy(gmn->lastname, my_bot->GetSurname().c_str()); + gmn->unknown[0] = 1; + gmn->unknown[1] = 1; + gmn->unknown[2] = 1; + gmn->unknown[3] = 1; + entity_list.QueueClients(my_bot->CastToClient(), outapp); + safe_delete(outapp); + c->Message(Chat::Yellow, "Bot Surname Saved."); + } +} + +void bot_subcommand_bot_title(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::Red, "You must specify a [title] to use this command. (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::Red, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::Red, "Title must be 31 characters or less."); + return; + } + std::string bot_title = sep->arg[1]; + bot_title = (bot_title == "-remove") ? "" : bot_title; + std::replace(bot_title.begin(), bot_title.end(), '_', ' '); + my_bot->SetTitle(bot_title); + if (!database.botdb.SaveBot(my_bot)) { + c->Message(Chat::Red, BotDatabase::fail::SaveBot()); + return; + } + else { + my_bot->CastToClient()->SetAATitle(my_bot->GetTitle().c_str()); + c->Message(Chat::Yellow, "Bot Title Saved."); + } +} + +void bot_subcommand_bot_suffix(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::Red, "You must specify a [suffix] to use this command. (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::Red, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::Red, "Suffix must be 31 characters or less."); + return; + } + std::string bot_suffix = sep->arg[1]; + bot_suffix = (bot_suffix == "-remove") ? "" : bot_suffix; + std::replace(bot_suffix.begin(), bot_suffix.end(), '_', ' '); + my_bot->SetSuffix(bot_suffix); + if (!database.botdb.SaveBot(my_bot)) { + c->Message(Chat::Red, BotDatabase::fail::SaveBot()); + return; + } + else { + my_bot->CastToClient()->SetTitleSuffix(my_bot->GetSuffix().c_str()); + c->Message(Chat::Yellow, "Bot Suffix Saved."); + } +} + void bot_subcommand_bot_report(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_subcommand_bot_report", sep->arg[0], "botreport")) @@ -5285,13 +5511,16 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) }; uint8 message_index = 0; - if (c->GetBotOptionSpawnMessageClassSpecific()) + if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) { message_index = VALIDATECLASSID(my_bot->GetClass()); + } - if (c->GetBotOptionSpawnMessageSay()) + if (c->GetBotOption(Client::booSpawnMessageSay)) { Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[message_index]); - else if (c->GetBotOptionSpawnMessageTell()) + } + else if (c->GetBotOption(Client::booSpawnMessageTell)) { c->Message(Chat::Tell, "%s tells you, \"%s\"", my_bot->GetCleanName(), bot_spawn_message[message_index]); + } } void bot_subcommand_bot_stance(Client *c, const Seperator *sep) @@ -5671,7 +5900,7 @@ void bot_subcommand_bot_update(Client *c, const Seperator *sep) continue; bot_iter->SetPetChooser(false); - bot_iter->CalcBotStats(c->GetBotOptionStatsUpdate()); + bot_iter->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); bot_iter->SendAppearancePacket(AT_WhoLevel, bot_iter->GetLevel(), true, true); ++bot_count; } @@ -7460,7 +7689,7 @@ void bot_subcommand_inventory_remove(Client *c, const Seperator *sep) } my_bot->BotRemoveEquipItem(slotId); - my_bot->CalcBotStats(c->GetBotOptionStatsUpdate()); + my_bot->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); } switch (slotId) { diff --git a/zone/bot_command.h b/zone/bot_command.h index d60238f23..c8bad1e42 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -614,8 +614,11 @@ void bot_subcommand_bot_report(Client *c, const Seperator *sep); void bot_subcommand_bot_spawn(Client *c, const Seperator *sep); void bot_subcommand_bot_stance(Client *c, const Seperator *sep); void bot_subcommand_bot_stop_melee_level(Client *c, const Seperator *sep); +void bot_subcommand_bot_suffix(Client *c, const Seperator *sep); void bot_subcommand_bot_summon(Client *c, const Seperator *sep); +void bot_subcommand_bot_surname(Client *c, const Seperator *sep); void bot_subcommand_bot_tattoo(Client *c, const Seperator *sep); +void bot_subcommand_bot_title(Client *c, const Seperator *sep); void bot_subcommand_bot_toggle_archer(Client *c, const Seperator *sep); void bot_subcommand_bot_toggle_helm(Client *c, const Seperator *sep); void bot_subcommand_bot_update(Client *c, const Seperator *sep); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index db2f2018b..931b927f6 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -29,6 +29,8 @@ #include "bot.h" #include "client.h" +#include + bool BotDatabase::LoadBotCommandSettings(std::map>> &bot_command_settings) { @@ -217,12 +219,19 @@ bool BotDatabase::LoadQuestableSpawnCount(const uint32 owner_id, int& spawn_coun return true; } -bool BotDatabase::LoadBotsList(const uint32 owner_id, std::list& bots_list) +bool BotDatabase::LoadBotsList(const uint32 owner_id, std::list& bots_list, bool ByAccount) { if (!owner_id) return false; - query = StringFormat("SELECT `bot_id`, `name`, `class`, `level`, `race`, `gender` FROM `bot_data` WHERE `owner_id` = '%u'", owner_id); + if (ByAccount == true) + query = StringFormat("SELECT bot_id, bd.`name`, bd.class, bd.`level`, bd.race, bd.gender, cd.`name` as owner, bd.owner_id, cd.account_id, cd.id" + " FROM bot_data as bd inner join character_data as cd on bd.owner_id = cd.id" + " WHERE cd.account_id = (select account_id from bot_data bd inner join character_data as cd on bd.owner_id = cd.id where bd.owner_id = '%u' LIMIT 1)" + " ORDER BY bd.owner_id", owner_id); + else + query = StringFormat("SELECT `bot_id`, `name`, `class`, `level`, `race`, `gender`, 'You' as owner, owner_id FROM `bot_data` WHERE `owner_id` = '%u'", owner_id); + auto results = database.QueryDatabase(query); if (!results.Success()) return false; @@ -240,12 +249,17 @@ bool BotDatabase::LoadBotsList(const uint32 owner_id, std::list 63) + bot_owner = bot_owner.substr(0, 63); + if (!bot_owner.empty()) + strcpy(bot_entry.Owner, bot_owner.c_str()); bot_entry.Class = atoi(row[2]); bot_entry.Level = atoi(row[3]); bot_entry.Race = atoi(row[4]); bot_entry.Gender = atoi(row[5]); - + bot_entry.Owner_ID = atoi(row[7]); bots_list.push_back(bot_entry); } @@ -320,8 +334,8 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot) " `spells_id`," " `name`," " `last_name`," - " `title`," /* planned use[4] */ - " `suffix`," /* planned use[5] */ + " `title`," + " `suffix`," " `zone_id`," " `gender`," " `race`," @@ -418,7 +432,9 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot) loaded_bot = new Bot(bot_id, atoi(row[0]), atoi(row[1]), atof(row[14]), atoi(row[6]), tempNPCStruct); if (loaded_bot) { loaded_bot->SetShowHelm((atoi(row[43]) > 0 ? true : false)); - + loaded_bot->SetSurname(row[3]);//maintaining outside mob::lastname to cater to spaces + loaded_bot->SetTitle(row[4]); + loaded_bot->SetSuffix(row[5]); uint32 bfd = atoi(row[44]); if (bfd < 1) bfd = 1; @@ -627,12 +643,14 @@ bool BotDatabase::SaveBot(Bot* bot_inst) " `corruption` = '%i'," " `show_helm` = '%i'," " `follow_distance` = '%i'," - " `stop_melee_level` = '%u'" + " `stop_melee_level` = '%u'," + " `title` = '%s'," + " `suffix` = '%s'" " WHERE `bot_id` = '%u'", bot_inst->GetBotOwnerCharacterID(), bot_inst->GetBotSpellID(), bot_inst->GetCleanName(), - bot_inst->GetLastName(), + bot_inst->GetSurname().c_str(), bot_inst->GetLastZoneID(), bot_inst->GetBaseGender(), bot_inst->GetBaseRace(), @@ -670,6 +688,8 @@ bool BotDatabase::SaveBot(Bot* bot_inst) ((bot_inst->GetShowHelm()) ? (1) : (0)), bot_inst->GetFollowDistance(), bot_inst->GetStopMeleeLevel(), + bot_inst->GetTitle().c_str(), + bot_inst->GetSuffix().c_str(), bot_inst->GetBotID() ); auto results = database.QueryDatabase(query); @@ -2207,111 +2227,92 @@ bool BotDatabase::SaveStopMeleeLevel(const uint32 owner_id, const uint32 bot_id, bool BotDatabase::LoadOwnerOptions(Client *owner) { - if (!owner || !owner->CharacterID()) - return false; - - query = StringFormat( - "SELECT `death_marquee`, `stats_update`, `spawn_message_enabled`, `spawn_message_type` FROM `bot_owner_options`" - " WHERE `owner_id` = '%u'", - owner->CharacterID() - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - if (!results.RowCount()) { - query = StringFormat("REPLACE INTO `bot_owner_options` (`owner_id`) VALUES ('%u')", owner->CharacterID()); - results = database.QueryDatabase(query); - + if (!owner || !owner->CharacterID()) { return false; } - auto row = results.begin(); - owner->SetBotOptionDeathMarquee((atoi(row[0]) != 0)); - owner->SetBotOptionStatsUpdate((atoi(row[1]) != 0)); - switch (atoi(row[2])) { - case 2: - owner->SetBotOptionSpawnMessageSay(); - break; - case 1: - owner->SetBotOptionSpawnMessageTell(); - break; + query = fmt::format("SELECT `option_type`, `option_value` FROM `bot_owner_options` WHERE `owner_id` = '{}'", owner->CharacterID()); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } + + for (auto row : results) { + + owner->SetBotOption(static_cast(atoul(row[0])), (atoul(row[1]) != 0)); + } + + return true; +} + +bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag) +{ + if (!owner_id) { + return false; + } + + switch (static_cast(type)) { + case Client::booDeathMarquee: + case Client::booStatsUpdate: + case Client::booSpawnMessageClassSpecific: { + + query = fmt::format( + "REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')", + owner_id, + type, + (flag == true ? 1 : 0) + ); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } + + return true; + } default: - owner->SetBotOptionSpawnMessageSilent(); - break; + return false; } - owner->SetBotOptionSpawnMessageClassSpecific((atoi(row[3]) != 0)); - - return true; } -bool BotDatabase::SaveOwnerOptionDeathMarquee(const uint32 owner_id, const bool flag) +bool BotDatabase::SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag) { - if (!owner_id) + if (!owner_id) { return false; + } - query = StringFormat( - "UPDATE `bot_owner_options`" - " SET `death_marquee` = '%u'" - " WHERE `owner_id` = '%u'", - (flag == true ? 1 : 0), - owner_id - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) + switch (static_cast(type.first)) { + case Client::booSpawnMessageSay: + case Client::booSpawnMessageTell: { + switch (static_cast(type.second)) { + case Client::booSpawnMessageSay: + case Client::booSpawnMessageTell: { + + query = fmt::format( + "REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}'), ('{}', '{}', '{}')", + owner_id, + type.first, + (flag.first == true ? 1 : 0), + owner_id, + type.second, + (flag.second == true ? 1 : 0) + ); + + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } + + return true; + } + default: + return false; + } + } + default: return false; - - return true; -} - -bool BotDatabase::SaveOwnerOptionStatsUpdate(const uint32 owner_id, const bool flag) -{ - if (!owner_id) - return false; - - query = StringFormat( - "UPDATE `bot_owner_options`" - " SET `stats_update` = '%u'" - " WHERE `owner_id` = '%u'", - (flag == true ? 1 : 0), - owner_id - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - - return true; -} - -bool BotDatabase::SaveOwnerOptionSpawnMessage(const uint32 owner_id, const bool say, const bool tell, const bool class_specific) -{ - if (!owner_id) - return false; - - uint8 enabled_value = 0; - if (say) - enabled_value = 2; - else if (tell) - enabled_value = 1; - - uint8 type_value = 0; - if (class_specific) - type_value = 1; - - query = StringFormat( - "UPDATE `bot_owner_options`" - " SET" - " `spawn_message_enabled` = '%u'," - " `spawn_message_type` = '%u'" - " WHERE `owner_id` = '%u'", - enabled_value, - type_value, - owner_id - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - - return true; + } } diff --git a/zone/bot_database.h b/zone/bot_database.h index 0040f4a45..82cf68f09 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -52,7 +52,7 @@ public: 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 LoadBotsList(const uint32 owner_id, std::list& bots_list); + 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); @@ -141,10 +141,9 @@ public: bool SaveStopMeleeLevel(const uint32 owner_id, const uint32 bot_id, const uint8 sml_value); bool LoadOwnerOptions(Client *owner); - bool SaveOwnerOptionDeathMarquee(const uint32 owner_id, const bool flag); - bool SaveOwnerOptionStatsUpdate(const uint32 owner_id, const bool flag); - bool SaveOwnerOptionSpawnMessage(const uint32 owner_id, const bool say, const bool tell, const bool class_specific); - + bool SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag); + bool SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag); + /* Bot bot-group functions */ bool QueryBotGroupExistence(const std::string& botgroup_name, bool& extant_flag); diff --git a/zone/bot_structs.h b/zone/bot_structs.h index d54c0c042..7ba32349b 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -32,6 +32,8 @@ struct BotsAvailableList { uint8 Level; uint16 Race; uint8 Gender; + char Owner[64]; + uint32 Owner_ID; }; struct BotGroup { diff --git a/zone/client.cpp b/zone/client.cpp index c1293be3f..ba65695c9 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -347,7 +347,11 @@ Client::Client(EQStreamInterface* ieqs) dev_tools_window_enabled = true; #ifdef BOTS - bot_owner_options = DefaultBotOwnerOptions; + bot_owner_options[booDeathMarquee] = false; + bot_owner_options[booStatsUpdate] = false; + bot_owner_options[booSpawnMessageSay] = false; + bot_owner_options[booSpawnMessageTell] = true; + bot_owner_options[booSpawnMessageClassSpecific] = true; #endif AI_Init(); @@ -9134,4 +9138,24 @@ glm::vec4 &Client::GetLastPositionBeforeBulkUpdate() void Client::SetLastPositionBeforeBulkUpdate(glm::vec4 in_last_position_before_bulk_update) { Client::last_position_before_bulk_update = in_last_position_before_bulk_update; -} \ No newline at end of file +} + +#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 diff --git a/zone/client.h b/zone/client.h index 88bee0b03..54a1260ae 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1626,39 +1626,26 @@ private: int client_max_level; #ifdef BOTS - struct BotOwnerOptions { - bool death_marquee; - bool stats_update; - bool spawn_message_say; - bool spawn_message_tell; - bool spawn_message_class_specific; - }; + - BotOwnerOptions bot_owner_options; - - const BotOwnerOptions DefaultBotOwnerOptions = { - false, // death_marquee - false, // stats_update - false, // spawn_message_say - true, // spawn_message_tell - true // spawn_message_class_specific - }; + public: - void SetBotOptionDeathMarquee(bool flag) { bot_owner_options.death_marquee = flag; } - void SetBotOptionStatsUpdate(bool flag) { bot_owner_options.stats_update = flag; } - void SetBotOptionSpawnMessageSay() { bot_owner_options.spawn_message_say = true; bot_owner_options.spawn_message_tell = false; } - void SetBotOptionSpawnMessageTell() { bot_owner_options.spawn_message_say = false; bot_owner_options.spawn_message_tell = true; } - void SetBotOptionSpawnMessageSilent() { bot_owner_options.spawn_message_say = false; bot_owner_options.spawn_message_tell = false; } - void SetBotOptionSpawnMessageClassSpecific(bool flag) { bot_owner_options.spawn_message_class_specific = flag; } + enum BotOwnerOption : size_t { + booDeathMarquee, + booStatsUpdate, + booSpawnMessageSay, + booSpawnMessageTell, + booSpawnMessageClassSpecific, + _booCount + }; - bool GetBotOptionDeathMarquee() const { return bot_owner_options.death_marquee; } - bool GetBotOptionStatsUpdate() const { return bot_owner_options.stats_update; } - bool GetBotOptionSpawnMessageSay() const { return bot_owner_options.spawn_message_say; } - bool GetBotOptionSpawnMessageTell() const { return bot_owner_options.spawn_message_tell; } - bool GetBotOptionSpawnMessageClassSpecific() const { return bot_owner_options.spawn_message_class_specific; } + bool GetBotOption(BotOwnerOption boo) const; + void SetBotOption(BotOwnerOption boo, bool flag = true); + +private: + bool bot_owner_options[_booCount]; -private: #endif }; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index d5dde9750..94b014243 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -3956,9 +3956,11 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) #ifdef BOTS // This block is necessary to clean up any bot objects owned by a Client Bot::BotOrderCampAll(this); - auto group = GetGroup(); - if (group && group->GroupCount() < 2) - group->DisbandGroup(); + // Evidently, this is bad under certain conditions and causes crashes... + // Group and Raid code really needs to be overhauled to account for non-client types (mercs and bots) + //auto group = GetGroup(); + //if (group && group->GroupCount() < 2) + // group->DisbandGroup(); #endif if (IsLFP()) worldserver.StopLFP(CharacterID()); diff --git a/zone/command.cpp b/zone/command.cpp index db9cd5524..761cecedd 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -182,6 +182,7 @@ int command_init(void) command_add("crashtest", "- Crash the zoneserver", 255, command_crashtest) || command_add("cvs", "- Summary of client versions currently online.", 200, command_cvs) || command_add("damage", "[amount] - Damage your target", 100, command_damage) || + command_add("databuckets", "View|Delete [key] [limit]- View data buckets, limit 50 default or Delete databucket by key", 80, command_databuckets) || command_add("date", "[yyyy] [mm] [dd] [HH] [MM] - Set EQ time", 90, command_date) || command_add("dbspawn2", "[spawngroup] [respawn] [variance] - Spawn an NPC from a predefined row in the spawn2 table", 100, command_dbspawn2) || command_add("delacct", "[accountname] - Delete an account", 150, command_delacct) || @@ -12637,6 +12638,85 @@ void command_scale(Client *c, const Seperator *sep) } } +void command_databuckets(Client *c, const Seperator *sep) + { + if (sep->arg[1][0] == 0) { + c->Message(Chat::Yellow, "Usage: #databuckets view (partial key)|(limit) OR #databuckets delete (key)"); + return; + } + if (strcasecmp(sep->arg[1], "view") == 0) { + + std::string key_filter; + uint8 limit = 50; + for (int i = 2; i < 4; i++) { + if (sep->arg[i][0] == '\0') + break; + if (strcasecmp(sep->arg[i], "limit") == 0) { + limit = (uint8)atoi(sep->arg[i + 1]); + continue; + } + } + if (sep->arg[2]) { + key_filter = str_tolower(sep->arg[2]); + } + std::string query = "SELECT `id`, `key`, `value`, `expires` FROM data_buckets"; + if (!key_filter.empty()) query += StringFormat(" WHERE `key` LIKE '%%%s%%'", key_filter.c_str()); + query += StringFormat(" LIMIT %u", limit); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + if (results.RowCount() == 0) { + c->Message(Chat::Yellow, "No data_buckets found"); + return; + } + int _ctr = 0; + // put in window for easier readability in case want command line for something else + std::string window_title = "Data Buckets"; + std::string window_text = + "" + "" + "" + "" + "" + "" + ""; + for (auto row = results.begin(); row != results.end(); ++row) { + auto id = static_cast(atoi(row[0])); + std::string key = row[1]; + std::string value = row[2]; + std::string expires = row[3]; + window_text.append(StringFormat( + "" + "" + "" + "" + "" + "", + id, + expires.c_str(), + key.c_str(), + value.c_str() + )); + _ctr++; + std::string del_saylink = StringFormat("#databuckets delete %s", key.c_str()); + c->Message(Chat::White, "%s : %s", + EQEmu::SayLinkEngine::GenerateQuestSaylink(del_saylink, false, "Delete").c_str(), key.c_str(), " Value: ", value.c_str()); + } + window_text.append("
IDExpiresKeyValue
%u%s%s%s
"); + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + std::string response = _ctr > 0 ? StringFormat("Found %i matching data buckets", _ctr).c_str() : "No Databuckets found."; + c->Message(Chat::Yellow, response.c_str()); + } + else if (strcasecmp(sep->arg[1], "delete") == 0) + { + if (DataBucket::DeleteData(sep->argplus[2])) + c->Message(Chat::Yellow, "data bucket %s deleted.", sep->argplus[2]); + else + c->Message(Chat::Red, "An error occurred deleting data bucket %s", sep->argplus[2]); + return; + } +} + void command_who(Client *c, const Seperator *sep) { std::string query = diff --git a/zone/command.h b/zone/command.h index af92ec3f8..b1b488d49 100644 --- a/zone/command.h +++ b/zone/command.h @@ -77,6 +77,7 @@ void command_crashtest(Client *c, const Seperator *sep); void command_cvs(Client *c, const Seperator *sep); void command_d1(Client *c, const Seperator *sep); void command_damage(Client *c, const Seperator *sep); +void command_databuckets(Client *c, const Seperator *sep); void command_date(Client *c, const Seperator *sep); void command_dbspawn2(Client *c, const Seperator *sep); void command_delacct(Client *c, const Seperator *sep); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 91f456768..2f0a3def2 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -15,6 +15,7 @@ #include #include +#include extern Zone* zone; @@ -4072,60 +4073,121 @@ bool ZoneDatabase::SetCharacterFactionLevel(uint32 char_id, int32 faction_id, in bool ZoneDatabase::LoadFactionData() { - std::string query = "SELECT MAX(id) FROM faction_list"; - auto results = QueryDatabase(query); - if (!results.Success()) { + std::string query("SELECT MAX(`id`) FROM `faction_list`"); + + auto faction_max_results = QueryDatabase(query); + if (!faction_max_results.Success() || faction_max_results.RowCount() == 0) { return false; } - if (results.RowCount() == 0) - return false; + auto fmr_row = faction_max_results.begin(); - auto row = results.begin(); + max_faction = atoul(fmr_row[0]); + faction_array = new Faction *[max_faction + 1]; - max_faction = row[0] ? atoi(row[0]) : 0; - faction_array = new Faction*[max_faction+1]; - for(unsigned int index=0; index faction_ids; + + // load factions + query = "SELECT `id`, `name`, `base` FROM `faction_list`"; - query = "SELECT id, name, base FROM faction_list"; - results = QueryDatabase(query); - if (!results.Success()) { + auto faction_results = QueryDatabase(query); + if (!faction_results.Success()) { return false; } - for (row = results.begin(); row != results.end(); ++row) { - uint32 index = atoi(row[0]); + for (auto fr_row : faction_results) { + + uint32 index = atoul(fr_row[0]); + if (index > max_faction) { + Log(Logs::General, Logs::Error, "Faction '%u' is out-of-bounds for faction array size!", index); + continue; + } + + // this should never hit since `id` is keyed..but, it alleviates any risk of lost pointers + if (faction_array[index] != nullptr) { + Log(Logs::General, Logs::Error, "Faction '%u' has already been assigned! (Duplicate Entry)", index); + continue; + } + faction_array[index] = new Faction; - strn0cpy(faction_array[index]->name, row[1], 50); - faction_array[index]->base = atoi(row[2]); + strn0cpy(faction_array[index]->name, fr_row[1], 50); + faction_array[index]->base = atoi(fr_row[2]); faction_array[index]->min = MIN_PERSONAL_FACTION; faction_array[index]->max = MAX_PERSONAL_FACTION; + + faction_ids.push_back(index); + } - // Load in the mimimum and maximum faction that can be earned for this faction - query = StringFormat("SELECT `min` , `max` FROM `faction_base_data` WHERE client_faction_id = %u", index); - auto baseResults = QueryDatabase(query); - if (!baseResults.Success() || baseResults.RowCount() == 0) { - Log(Logs::General, Logs::General, "Faction %d has no base data", (int)index); - } - else { - for (auto modRow = baseResults.begin(); modRow != baseResults.end(); ++modRow) { - faction_array[index]->min = atoi(modRow[0]); - faction_array[index]->max = atoi(modRow[1]); - Log(Logs::General, Logs::None, "Min(%d), Max(%d) for faction (%u)",faction_array[index]->min, faction_array[index]->max, index); + Log(Logs::General, Logs::Status, "%u Faction%s loaded...", faction_ids.size(), (faction_ids.size() == 1 ? "" : "s")); + + // this can be removed once the 'io_work' branch has been merged + std::vector faction_id_strings; + for (auto id : faction_ids) { + faction_id_strings.push_back(fmt::format("'{}'", id)); + } + const std::string faction_id_criteria(implode(",", faction_id_strings)); + + // code to activate (note above) + //const std::string faction_id_criteria(implode(",", std::pair('\'', '\''), faction_ids)); + + // load faction mins/maxes + query = fmt::format("SELECT `client_faction_id`, `min`, `max` FROM `faction_base_data` WHERE `client_faction_id` IN ({})", faction_id_criteria); + + auto base_results = QueryDatabase(query); + if (base_results.Success()) { + + for (auto br_row : base_results) { + + uint32 index = atoul(br_row[0]); + if (index > max_faction) { + Log(Logs::General, Logs::Error, "Faction '%u' is out-of-bounds for faction array size in Base adjustment!", index); + continue; } + + if (faction_array[index] == nullptr) { + Log(Logs::General, Logs::Error, "Faction '%u' does not exist for Base adjustment!", index); + continue; + } + + faction_array[index]->min = atoi(br_row[1]); + faction_array[index]->max = atoi(br_row[2]); } - // Load in modifiers to the faction based on characters race, class and diety. - query = StringFormat("SELECT `mod`, `mod_name` FROM `faction_list_mod` WHERE faction_id = %u", index); - auto modResults = QueryDatabase(query); - if (!modResults.Success()) - continue; + Log(Logs::General, Logs::Status, "%u Faction Base%s loaded...", base_results.RowCount(), (base_results.RowCount() == 1 ? "" : "s")); + } + else { + Log(Logs::General, Logs::Status, "Unable to load Faction Base data..."); + } + + // load race, class and diety modifiers + query = fmt::format("SELECT `faction_id`, `mod`, `mod_name` FROM `faction_list_mod` WHERE `faction_id` IN ({})", faction_id_criteria); - for (auto modRow = modResults.begin(); modRow != modResults.end(); ++modRow) { - faction_array[index]->mods[modRow[1]] = atoi(modRow[0]); + auto modifier_results = QueryDatabase(query); + if (modifier_results.Success()) { + + for (auto mr_row : modifier_results) { + + uint32 index = atoul(mr_row[0]); + if (index > max_faction) { + Log(Logs::General, Logs::Error, "Faction '%u' is out-of-bounds for faction array size in Modifier adjustment!", index); + continue; + } + + if (faction_array[index] == nullptr) { + Log(Logs::General, Logs::Error, "Faction '%u' does not exist for Modifier adjustment!", index); + continue; + } + + faction_array[index]->mods[mr_row[2]] = atoi(mr_row[1]); } - } + + Log(Logs::General, Logs::Status, "%u Faction Modifier%s loaded...", modifier_results.RowCount(), (modifier_results.RowCount() == 1 ? "" : "s")); + } + else { + Log(Logs::General, Logs::Status, "Unable to load Faction Modifier data..."); + } return true; }