diff --git a/common/database_schema.h b/common/database_schema.h index 781b46a95..b5f015d5a 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -403,6 +403,7 @@ namespace DatabaseSchema { "bot_pet_inventories", "bot_pets", "bot_spell_casting_chances", + "bot_spell_settings", "bot_spells_entries", "bot_stances", "bot_timers", diff --git a/common/repositories/base/base_bot_spell_settings_repository.h b/common/repositories/base/base_bot_spell_settings_repository.h new file mode 100644 index 000000000..3fe5600fc --- /dev/null +++ b/common/repositories/base/base_bot_spell_settings_repository.h @@ -0,0 +1,403 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories + */ + +#ifndef EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H +#define EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseBotSpellSettingsRepository { +public: + struct BotSpellSettings { + uint32_t id; + int32_t bot_id; + int16_t spell_id; + int16_t priority; + uint16_t min_level; + uint16_t max_level; + int16_t min_hp; + int16_t max_hp; + uint8_t is_enabled; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "bot_id", + "spell_id", + "priority", + "min_level", + "max_level", + "min_hp", + "max_hp", + "is_enabled", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "bot_id", + "spell_id", + "priority", + "min_level", + "max_level", + "min_hp", + "max_hp", + "is_enabled", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("bot_spell_settings"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static BotSpellSettings NewEntity() + { + BotSpellSettings e{}; + + e.id = 0; + e.bot_id = 0; + e.spell_id = 0; + e.priority = 0; + e.min_level = 0; + e.max_level = 255; + e.min_hp = 0; + e.max_hp = 0; + e.is_enabled = 1; + + return e; + } + + static BotSpellSettings GetBotSpellSettings( + const std::vector &bot_spell_settingss, + int bot_spell_settings_id + ) + { + for (auto &bot_spell_settings : bot_spell_settingss) { + if (bot_spell_settings.id == bot_spell_settings_id) { + return bot_spell_settings; + } + } + + return NewEntity(); + } + + static BotSpellSettings FindOne( + Database& db, + int bot_spell_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE id = {} LIMIT 1", + BaseSelect(), + bot_spell_settings_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + BotSpellSettings e{}; + + e.id = static_cast(strtoul(row[0], nullptr, 10)); + e.bot_id = static_cast(atoi(row[1])); + e.spell_id = static_cast(atoi(row[2])); + e.priority = static_cast(atoi(row[3])); + e.min_level = static_cast(strtoul(row[4], nullptr, 10)); + e.max_level = static_cast(strtoul(row[5], nullptr, 10)); + e.min_hp = static_cast(atoi(row[6])); + e.max_hp = static_cast(atoi(row[7])); + e.is_enabled = static_cast(strtoul(row[8], nullptr, 10)); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int bot_spell_settings_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + bot_spell_settings_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const BotSpellSettings &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.id)); + v.push_back(columns[1] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[2] + " = " + std::to_string(e.spell_id)); + v.push_back(columns[3] + " = " + std::to_string(e.priority)); + v.push_back(columns[4] + " = " + std::to_string(e.min_level)); + v.push_back(columns[5] + " = " + std::to_string(e.max_level)); + v.push_back(columns[6] + " = " + std::to_string(e.min_hp)); + v.push_back(columns[7] + " = " + std::to_string(e.max_hp)); + v.push_back(columns[8] + " = " + std::to_string(e.is_enabled)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static BotSpellSettings InsertOne( + Database& db, + BotSpellSettings e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.priority)); + v.push_back(std::to_string(e.min_level)); + v.push_back(std::to_string(e.max_level)); + v.push_back(std::to_string(e.min_hp)); + v.push_back(std::to_string(e.max_hp)); + v.push_back(std::to_string(e.is_enabled)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.priority)); + v.push_back(std::to_string(e.min_level)); + v.push_back(std::to_string(e.max_level)); + v.push_back(std::to_string(e.min_hp)); + v.push_back(std::to_string(e.max_hp)); + v.push_back(std::to_string(e.is_enabled)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSpellSettings e{}; + + e.id = static_cast(strtoul(row[0], nullptr, 10)); + e.bot_id = static_cast(atoi(row[1])); + e.spell_id = static_cast(atoi(row[2])); + e.priority = static_cast(atoi(row[3])); + e.min_level = static_cast(strtoul(row[4], nullptr, 10)); + e.max_level = static_cast(strtoul(row[5], nullptr, 10)); + e.min_hp = static_cast(atoi(row[6])); + e.max_hp = static_cast(atoi(row[7])); + e.is_enabled = static_cast(strtoul(row[8], nullptr, 10)); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + BotSpellSettings e{}; + + e.id = static_cast(strtoul(row[0], nullptr, 10)); + e.bot_id = static_cast(atoi(row[1])); + e.spell_id = static_cast(atoi(row[2])); + e.priority = static_cast(atoi(row[3])); + e.min_level = static_cast(strtoul(row[4], nullptr, 10)); + e.max_level = static_cast(strtoul(row[5], nullptr, 10)); + e.min_hp = static_cast(atoi(row[6])); + e.max_hp = static_cast(atoi(row[7])); + e.is_enabled = static_cast(strtoul(row[8], nullptr, 10)); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + +}; + +#endif //EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H diff --git a/common/repositories/bot_spell_settings_repository.h b/common/repositories/bot_spell_settings_repository.h new file mode 100644 index 000000000..d606b0c0e --- /dev/null +++ b/common/repositories/bot_spell_settings_repository.h @@ -0,0 +1,77 @@ +#ifndef EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H +#define EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_bot_spell_settings_repository.h" + +class BotSpellSettingsRepository: public BaseBotSpellSettingsRepository { +public: + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * BotSpellSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * BotSpellSettingsRepository::GetWhereNeverExpires() + * BotSpellSettingsRepository::GetWhereXAndY() + * BotSpellSettingsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + + static bool UpdateSpellSetting( + Database& db, + const BotSpellSettings &e + ) { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[3] + " = " + std::to_string(e.priority)); + v.push_back(columns[4] + " = " + std::to_string(e.min_level)); + v.push_back(columns[5] + " = " + std::to_string(e.max_level)); + v.push_back(columns[6] + " = " + std::to_string(e.min_hp)); + v.push_back(columns[7] + " = " + std::to_string(e.max_hp)); + v.push_back(columns[8] + " = " + std::to_string(e.is_enabled)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE `bot_id` = {} AND `spell_id` = {}", + TableName(), + Strings::Implode(", ", v), + e.bot_id, + e.spell_id + ) + ); + + return (results.Success() ? true : false); + } +}; + +#endif //EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H diff --git a/common/version.h b/common/version.h index b0df19344..a4e987567 100644 --- a/common/version.h +++ b/common/version.h @@ -37,7 +37,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9212 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9032 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9033 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 040ff3ec4..7dd8f2a71 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -31,6 +31,7 @@ 9030|2022_10_27_bot_data_buckets.sql|SHOW COLUMNS FROM `bot_spells_entries` LIKE 'bucket_name'|empty| 9031|2022_11_13_bot_spells_entries.sql|SELECT * FROM db_version WHERE bots_version >= 9031|empty| 9032|2022_11_07_bot_expansion_bitmask.sql|SHOW COLUMNS FROM `bot_data` LIKE 'expansion_bitmask'|empty| +9033|2022_11_19_bot_spell_settings.sql|SHOW TABLES LIKE 'bot_spell_settings'|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/2022_11_19_bot_spell_settings.sql b/utils/sql/git/bots/required/2022_11_19_bot_spell_settings.sql new file mode 100644 index 000000000..0224034aa --- /dev/null +++ b/utils/sql/git/bots/required/2022_11_19_bot_spell_settings.sql @@ -0,0 +1,12 @@ +CREATE TABLE `bot_spell_settings` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `bot_id` int(11) NOT NULL DEFAULT 0, + `spell_id` smallint(5) NOT NULL DEFAULT 0, + `priority` smallint(5) NOT NULL DEFAULT 0, + `min_level` smallint(5) unsigned NOT NULL DEFAULT 0, + `max_level` smallint(5) unsigned NOT NULL DEFAULT 255, + `min_hp` smallint(5) NOT NULL DEFAULT 0, + `max_hp` smallint(5) NOT NULL DEFAULT 0, + `is_enabled` tinyint(1) unsigned NOT NULL DEFAULT 1, + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/zone/bot.cpp b/zone/bot.cpp index 089977ddd..7fe72733a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -25,6 +25,7 @@ #include "lua_parser.h" #include "../common/strings.h" #include "../common/say_link.h" +#include "../common/repositories/bot_spell_settings_repository.h" extern volatile bool is_zone_loaded; @@ -407,6 +408,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to GetBotOwnerDataBuckets(); GetBotDataBuckets(); + LoadBotSpellSettings(); AI_AddBotSpells(GetBotSpellID()); CalcBotStats(false); @@ -10538,18 +10540,24 @@ bool Bot::GetBotOwnerDataBuckets() return false; } - auto query = fmt::format( + const auto query = fmt::format( "SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'", Strings::Escape(bot_owner->GetBucketKey()) ); - auto results = database.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + auto results = database.QueryDatabase(query); + if (!results.Success()) { return false; } + bot_owner_data_buckets.clear(); + + if (!results.RowCount()) { + return true; + } + for (auto row : results) { - bot_data_buckets.insert(std::pair(row[0], row[1])); + bot_owner_data_buckets.insert(std::pair(row[0], row[1])); } return true; @@ -10557,16 +10565,22 @@ bool Bot::GetBotOwnerDataBuckets() bool Bot::GetBotDataBuckets() { - auto query = fmt::format( + const auto query = fmt::format( "SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'", Strings::Escape(GetBucketKey()) ); - auto results = database.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + auto results = database.QueryDatabase(query); + if (!results.Success()) { return false; } + bot_data_buckets.clear(); + + if (!results.RowCount()) { + return true; + } + for (auto row : results) { bot_data_buckets.insert(std::pair(row[0], row[1])); } @@ -10591,7 +10605,7 @@ bool Bot::CheckDataBucket(std::string bucket_name, std::string bucket_value, uin bucket_name ); - player_value = bot_data_buckets[full_name]; + player_value = bot_owner_data_buckets[full_name]; if (player_value.empty()) { return false; } @@ -10635,6 +10649,292 @@ void Bot::SetExpansionBitmask(int expansion_bitmask, bool save) LoadAAs(); } +bool Bot::AddBotSpellSetting(uint16 spell_id, BotSpellSetting* bs) +{ + if (!IsValidSpell(spell_id) || !bs) { + return false; + } + + auto obs = GetBotSpellSetting(spell_id); + if (obs) { + return false; + } + + auto s = BotSpellSettingsRepository::NewEntity(); + + s.spell_id = spell_id; + s.bot_id = GetBotID(); + + s.priority = bs->priority; + s.min_level = bs->min_level; + s.max_level = bs->max_level; + s.min_hp = bs->min_hp; + s.max_hp = bs->max_hp; + s.is_enabled = bs->is_enabled; + + const auto& nbs = BotSpellSettingsRepository::InsertOne(content_db, s); + if (!nbs.id) { + return false; + } + + LoadBotSpellSettings(); + return true; +} + +bool Bot::DeleteBotSpellSetting(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) { + return false; + } + + auto bs = GetBotSpellSetting(spell_id); + if (!bs) { + return false; + } + + BotSpellSettingsRepository::DeleteWhere( + content_db, + fmt::format( + "bot_id = {} AND spell_id = {}", + GetBotID(), + spell_id + ) + ); + LoadBotSpellSettings(); + return true; +} + +BotSpellSetting* Bot::GetBotSpellSetting(uint16 spell_id) +{ + if (!IsValidSpell(spell_id) || !bot_spell_settings.count(spell_id)) { + return nullptr; + } + + auto b = bot_spell_settings.find(spell_id); + if (b != bot_spell_settings.end()) { + return &b->second; + } + + return nullptr; +} + +void Bot::ListBotSpells() +{ + auto bot_owner = GetBotOwner(); + if (!bot_owner) { + return; + } + + if (AIBot_spells.empty()) { + bot_owner->Message( + Chat::White, + fmt::format( + "{} has no AI Spells.", + GetCleanName() + ).c_str() + ); + return; + } + + auto spell_count = 0; + auto spell_number = 1; + + for (const auto& s : AIBot_spells) { + bot_owner->Message( + Chat::White, + fmt::format( + "Spell {} | Spell: {} ({})", + spell_number, + spells[s.spellid].name, + s.spellid + ).c_str() + ); + + bot_owner->Message( + Chat::White, + fmt::format( + "Spell {} | Priority: {} Health: {}", + spell_number, + s.priority, + GetHPString(s.min_hp, s.max_hp) + ).c_str() + ); + + spell_count++; + spell_number++; + } + + bot_owner->Message( + Chat::White, + fmt::format( + "{} has {} AI Spell{}.", + GetCleanName(), + spell_count, + spell_count != 1 ? "s" :"" + ).c_str() + ); +} + +void Bot::ListBotSpellSettings() +{ + auto bot_owner = GetBotOwner(); + if (!bot_owner) { + return; + } + + if (!bot_spell_settings.size()) { + bot_owner->Message( + Chat::White, + fmt::format( + "{} does not have any spell settings.", + GetCleanName() + ).c_str() + ); + return; + } + + auto setting_count = 0; + auto setting_number = 1; + + for (const auto& bs : bot_spell_settings) { + bot_owner->Message( + Chat::White, + fmt::format( + "Setting {} | Spell: {} ({}) Enabled: {}", + setting_number, + spells[bs.first].name, + bs.first, + bs.second.is_enabled ? "Yes" : "No" + ).c_str() + ); + + bot_owner->Message( + Chat::White, + fmt::format( + "Setting {} | Priority: {} Levels: {} Health: {}", + setting_number, + bs.second.priority, + GetLevelString(bs.second.min_level, bs.second.max_level), + GetHPString(bs.second.min_hp, bs.second.max_hp) + ).c_str() + ); + + setting_count++; + setting_number++; + } + + bot_owner->Message( + Chat::White, + fmt::format( + "{} has {} spell setting{}.", + GetCleanName(), + setting_count, + setting_count != 1 ? "s" : "" + ).c_str() + ); +} + +void Bot::LoadBotSpellSettings() +{ + bot_spell_settings.clear(); + + auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID())); + if (s.empty()) { + return; + } + + for (const auto& e : s) { + BotSpellSetting b; + + b.priority = e.priority; + b.min_level = e.min_level; + b.max_level = e.max_level; + b.min_hp = e.min_hp; + b.max_hp = e.max_hp; + b.is_enabled = e.is_enabled; + + bot_spell_settings[e.spell_id] = b; + } +} + +bool Bot::UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs) +{ + if (!IsValidSpell(spell_id) || !bs) { + return false; + } + + auto s = BotSpellSettingsRepository::NewEntity(); + + s.spell_id = spell_id; + s.bot_id = GetBotID(); + s.priority = bs->priority; + s.min_level = bs->min_level; + s.max_level = bs->max_level; + s.min_hp = bs->min_hp; + s.max_hp = bs->max_hp; + s.is_enabled = bs->is_enabled; + + auto obs = GetBotSpellSetting(spell_id); + if (!obs) { + return false; + } + + if (!BotSpellSettingsRepository::UpdateSpellSetting(content_db, s)) { + return false; + } + + LoadBotSpellSettings(); + return true; +} + +std::string Bot::GetLevelString(uint8 min_level, uint8 max_level) +{ + std::string level_string = "Any"; + if (min_level && max_level) { + level_string = fmt::format( + "{} to {}", + min_level, + max_level + ); + } else if (min_level && !max_level) { + level_string = fmt::format( + "{}+", + min_level + ); + } else if (!min_level && max_level) { + level_string = fmt::format( + "1 to {}", + max_level + ); + } + + return level_string; +} + +std::string Bot::GetHPString(int8 min_hp, int8 max_hp) +{ + std::string hp_string = "Any"; + if (min_hp && max_hp) { + hp_string = fmt::format( + "{}%% to {}%%", + min_hp, + max_hp + ); + } else if (min_hp && !max_hp) { + hp_string = fmt::format( + "{}%% to 100%%", + min_hp + ); + } else if (!min_hp && max_hp) { + hp_string = fmt::format( + "1%% to {}%%", + max_hp + ); + } + + return hp_string; +} + uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; #endif diff --git a/zone/bot.h b/zone/bot.h index 62746f1a0..3e8f27b22 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -307,7 +307,7 @@ public: void DoEnduranceRegen(); //This Regenerates endurance void DoEnduranceUpkeep(); //does the endurance upkeep - bool AI_AddBotSpells(uint32 iDBSpellsID); + bool AI_AddBotSpells(uint32 bot_spell_id); void AddSpellToBotList( int16 iPriority, uint16 iSpellID, @@ -470,6 +470,7 @@ public: //static void UpdateRaidCastingRoles(const Raid* raid, bool disband = false); bool IsBotCaster() { return IsCasterClass(GetClass()); } + bool IsBotHybrid() { return IsHybridClass(GetClass()); } bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); } bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); } bool IsBotSpellFighter() { return IsSpellFighterClass(GetClass()); } @@ -596,6 +597,18 @@ public: int GetExpansionBitmask(); void SetExpansionBitmask(int expansion_bitmask, bool save = true); + void ListBotSpells(); + + std::string GetLevelString(uint8 min_level, uint8 max_level); + std::string GetHPString(int8 min_hp, int8 max_hp); + + bool AddBotSpellSetting(uint16 spell_id, BotSpellSetting* bs); + bool DeleteBotSpellSetting(uint16 spell_id); + BotSpellSetting* GetBotSpellSetting(uint16 spell_id); + void ListBotSpellSettings(); + void LoadBotSpellSettings(); + bool UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs); + static void SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leader_id); std::string CreateSayLink(Client* botOwner, const char* message, const char* name); @@ -762,6 +775,9 @@ private: BotCastingRoles m_CastingRoles; std::map bot_data_buckets; + std::map bot_owner_data_buckets; + + std::map bot_spell_settings; std::shared_ptr m_member_of_heal_rotation; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index cd1ff5706..d1839b01c 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1416,6 +1416,12 @@ int bot_command_init(void) bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) || bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) || bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) || + bot_command_add("spelllist", "Lists a Caster of Hybrid bot's spells", AccountStatus::Player, bot_command_spell_list) || + bot_command_add("spellsettingsadd", "Add a bot spell setting for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_add) || + bot_command_add("spellsettingsdelete", "Delete a bot spell setting from a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_delete) || + bot_command_add("spellsettingslist", "Lists a Caster or Hybrid bot's spell settings", AccountStatus::Player, bot_command_spell_settings_list) || + bot_command_add("spellsettingstoggle", "Toggle a bot spell for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_toggle) || + bot_command_add("spellsettingsupdate", "Update a bot spell setting for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_update) || bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || @@ -10086,7 +10092,7 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b if (local_entry->single != single_flag) { continue; } - + msg = fmt::format( "{}circle {}{}", std::to_string(BOT_COMMAND_CHAR), @@ -10122,7 +10128,7 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b if (local_entry->single != single_flag) { continue; } - + msg = fmt::format( "{}portal {}{}", std::to_string(BOT_COMMAND_CHAR), @@ -10226,4 +10232,514 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp return false; } +void bot_command_spell_list(Client* c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_list", sep->arg[0], "spelllist")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + my_bot->ListBotSpells(); +} + +void bot_command_spell_settings_add(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_add", sep->arg[0], "spellsettingsadd")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 6 || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) || + !sep->IsNumber(4) || + !sep->IsNumber(5) || + !sep->IsNumber(6) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + if (my_bot->GetBotSpellSetting(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "{} already has a spell setting for {} ({}), trying using {} instead.", + my_bot->GetCleanName(), + spells[spell_id].name, + spell_id, + Saylink::Silent("^spellsettingsupdate") + ).c_str() + ); + return; + } + + auto priority = static_cast(std::stoi(sep->arg[2])); + auto min_level = static_cast(std::stoul(sep->arg[3])); + auto max_level = static_cast(std::stoul(sep->arg[4])); + auto min_hp = static_cast(EQ::Clamp(std::stoi(sep->arg[5]), -1, 99)); + auto max_hp = static_cast(EQ::Clamp(std::stoi(sep->arg[6]), -1, 100)); + + BotSpellSetting bs; + + bs.priority = priority; + bs.min_level = min_level; + bs.max_level = max_level; + bs.min_hp = min_hp; + bs.max_hp = max_hp; + + if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to add spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully added spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Added | Spell: {} ({}) ", + spells[spell_id].name, + spell_id + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Added | Priority: {} Levels: {} Health: {}", + priority, + my_bot->GetLevelString(min_level, max_level), + my_bot->GetHPString(min_hp, max_hp) + ).c_str() + ); +} + +void bot_command_spell_settings_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_delete", sep->arg[0], "spellsettingsdelete")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 1 || + !sep->IsNumber(1) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + if (!my_bot->DeleteBotSpellSetting(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Failed to delete spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully deleted spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Deleted | Spell: {} ({})", + spells[spell_id].name, + spell_id + ).c_str() + ); +} + +void bot_command_spell_settings_list(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_list", sep->arg[0], "spellsettingslist")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {}", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + my_bot->ListBotSpellSettings(); +} + +void bot_command_spell_settings_toggle(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_toggle", sep->arg[0], "spellsettingstoggle")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Toggle]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 2 || + !sep->IsNumber(1) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Toggle]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + bool toggle = ( + sep->IsNumber(2) ? + (std::stoi(sep->arg[2]) ? true : false) : + atobool(sep->arg[2]) + ); + + auto obs = my_bot->GetBotSpellSetting(spell_id); + if (!obs) { + return; + } + + BotSpellSetting bs; + + bs.priority = obs->priority; + bs.min_level = obs->min_level; + bs.max_level = obs->max_level; + bs.min_hp = obs->min_hp; + bs.max_hp = obs->max_hp; + bs.is_enabled = toggle; + + if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to {}able spell for {}.", + toggle ? "en" : "dis", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully {}abled spell for {}.", + toggle ? "en" : "dis", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell {}abled | Spell: {} ({})", + toggle ? "En" : "Dis", + spells[spell_id].name, + spell_id + ).c_str() + ); +} + +void bot_command_spell_settings_update(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_update", sep->arg[0], "spellsettingsupdate")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) { + c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 6 || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) || + !sep->IsNumber(4) || + !sep->IsNumber(5) || + !sep->IsNumber(6) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(std::stoul(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + auto priority = static_cast(std::stoi(sep->arg[2])); + auto min_level = static_cast(std::stoul(sep->arg[3])); + auto max_level = static_cast(std::stoul(sep->arg[4])); + auto min_hp = static_cast(EQ::Clamp(std::stoi(sep->arg[5]), -1, 99)); + auto max_hp = static_cast(EQ::Clamp(std::stoi(sep->arg[6]), -1, 100)); + + BotSpellSetting bs; + + bs.priority = priority; + bs.min_level = min_level; + bs.max_level = max_level; + bs.min_hp = min_hp; + bs.max_hp = max_hp; + + if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to update spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully updated spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Updated | Spell: {} ({})", + spells[spell_id].name, + spell_id + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Updated | Priority: {} Levels: {} Health: {}", + priority, + my_bot->GetLevelString(min_level, max_level), + my_bot->GetHPString(min_hp, max_hp) + ).c_str() + ); +} + #endif // BOTS diff --git a/zone/bot_command.h b/zone/bot_command.h index 15c11a025..ad341c123 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -526,7 +526,6 @@ typedef std::map> bcst_required_bot typedef std::map bcst_levels; typedef std::map bcst_levels_map; - #define BOT_COMMAND_CHAR '^' typedef void (*BotCmdFuncPtr)(Client *,const Seperator *); @@ -540,8 +539,7 @@ typedef struct { extern int (*bot_command_dispatch)(Client *,char const*); extern int bot_command_count; // number of bot commands loaded - -// the bot command system: +// Bot Command System: int bot_command_init(void); void bot_command_deinit(void); int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function); @@ -549,8 +547,7 @@ int bot_command_not_avail(Client *c, const char *message); int bot_command_real_dispatch(Client *c, char const *message); void bot_command_log_command(Client *c, const char *message); - -// bot commands +// Bot Commands void bot_command_actionable(Client *c, const Seperator *sep); void bot_command_aggressive(Client *c, const Seperator *sep); void bot_command_apply_poison(Client *c, const Seperator *sep); @@ -589,6 +586,12 @@ void bot_command_resurrect(Client *c, const Seperator *sep); void bot_command_rune(Client *c, const Seperator *sep); void bot_command_send_home(Client *c, const Seperator *sep); void bot_command_size(Client *c, const Seperator *sep); +void bot_command_spell_list(Client* c, const Seperator *sep); +void bot_command_spell_settings_add(Client* c, const Seperator *sep); +void bot_command_spell_settings_delete(Client* c, const Seperator *sep); +void bot_command_spell_settings_list(Client* c, const Seperator *sep); +void bot_command_spell_settings_toggle(Client* c, const Seperator *sep); +void bot_command_spell_settings_update(Client* c, const Seperator *sep); void bot_command_summon_corpse(Client *c, const Seperator *sep); void bot_command_suspend(Client *c, const Seperator *sep); void bot_command_taunt(Client *c, const Seperator *sep); @@ -596,8 +599,7 @@ void bot_command_track(Client *c, const Seperator *sep); void bot_command_view_combos(Client *c, const Seperator *sep); void bot_command_water_breathing(Client *c, const Seperator *sep); - -// bot subcommands +// Bot Subcommands void bot_subcommand_bot_appearance(Client *c, const Seperator *sep); void bot_subcommand_bot_beard_color(Client *c, const Seperator *sep); void bot_subcommand_bot_beard_style(Client *c, const Seperator *sep); diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 7ba32349b..936e66abf 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -79,6 +79,15 @@ struct BotAA { uint8 total_levels; }; +struct BotSpellSetting { + int16 priority; + uint8 min_level; + uint8 max_level; + int8 min_hp; + int8 max_hp; + bool is_enabled; +}; + #endif // BOTS #endif // BOT_STRUCTS diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index cf98eec33..b70d4e7f8 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -21,6 +21,8 @@ #include "bot.h" #include "../common/data_verification.h" #include "../common/strings.h" +#include "../common/repositories/bot_spells_entries_repository.h" +#include "../common/repositories/npc_spells_repository.h" #if EQDEBUG >= 12 #define BotAI_DEBUG_Spells 25 @@ -2877,16 +2879,16 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index); } -bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { +bool Bot::AI_AddBotSpells(uint32 bot_spell_id) { // ok, this function should load the list, and the parent list then shove them into the struct and sort - npc_spells_id = iDBSpellsID; + npc_spells_id = bot_spell_id; AIBot_spells.clear(); - if (!iDBSpellsID) { + if (!bot_spell_id) { AIautocastspell_timer->Disable(); return false; } - auto* spell_list = content_db.GetBotSpells(iDBSpellsID); + auto* spell_list = content_db.GetBotSpells(bot_spell_id); if (!spell_list) { AIautocastspell_timer->Disable(); return false; @@ -2897,7 +2899,7 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { auto debug_msg = fmt::format( "Loading NPCSpells onto {}: dbspellsid={}, level={}", GetName(), - iDBSpellsID, + bot_spell_id, GetLevel() ); @@ -2993,7 +2995,50 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { continue; } } - AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp, e.bucket_name, e.bucket_value, e.bucket_comparison); + + const auto& bs = GetBotSpellSetting(e.spellid); + if (bs) { + if (!bs->is_enabled) { + continue; + } + + if (bs->min_level > 0 && GetLevel() < bs->min_level) { + continue; + } + + if (bs->max_level > 0 && GetLevel() > bs->max_level) { + continue; + } + + AddSpellToBotList( + bs->priority, + e.spellid, + e.type, + e.manacost, + e.recast_delay, + e.resist_adjust, + bs->min_hp, + bs->max_hp, + e.bucket_name, + e.bucket_value, + e.bucket_comparison + ); + continue; + } + + AddSpellToBotList( + e.priority, + e.spellid, + e.type, + e.manacost, + e.recast_delay, + e.resist_adjust, + e.min_hp, + e.max_hp, + e.bucket_name, + e.bucket_value, + e.bucket_comparison + ); } } } @@ -3049,7 +3094,50 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { continue; } } - AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp, e.bucket_name, e.bucket_value, e.bucket_comparison); + + const auto& bs = GetBotSpellSetting(e.spellid); + if (bs) { + if (!bs->is_enabled) { + continue; + } + + if (bs->min_level > 0 && GetLevel() < bs->min_level) { + continue; + } + + if (bs->max_level > 0 && GetLevel() > bs->max_level) { + continue; + } + + AddSpellToBotList( + bs->priority, + e.spellid, + e.type, + e.manacost, + e.recast_delay, + e.resist_adjust, + bs->min_hp, + bs->max_hp, + e.bucket_name, + e.bucket_value, + e.bucket_comparison + ); + continue; + } + + AddSpellToBotList( + e.priority, + e.spellid, + e.type, + e.manacost, + e.recast_delay, + e.resist_adjust, + e.min_hp, + e.max_hp, + e.bucket_name, + e.bucket_value, + e.bucket_comparison + ); } } @@ -3108,108 +3196,91 @@ bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) { return it != spell_list->entries.end(); } -DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID) +DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id) { - if (!iDBSpellsID) { + if (!bot_spell_id) { return nullptr; } - auto it = Bot_Spells_Cache.find(iDBSpellsID); - - if (it != Bot_Spells_Cache.end()) { // it's in the cache, easy =) - return &it->second; + auto c = bot_spells_cache.find(bot_spell_id); + if (c != bot_spells_cache.end()) { // it's in the cache, easy =) + return &c->second; } - if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already - Bot_Spells_LoadTried.insert(iDBSpellsID); + if (!bot_spells_loadtried.count(bot_spell_id)) { // no reason to ask the DB again if we have failed once already + bot_spells_loadtried.insert(bot_spell_id); - auto query = fmt::format( - "SELECT id, parent_list, attack_proc, proc_chance, " - "range_proc, rproc_chance, defensive_proc, dproc_chance, " - "fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, " - "engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, " - "pursue_no_sp_recast_min, pursue_no_sp_recast_max, " - "pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, " - "idle_b_chance FROM npc_spells WHERE id = {}", - iDBSpellsID - ); - - auto results = QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + auto n = NpcSpellsRepository::FindOne(content_db, bot_spell_id); + if (!n.id) { return nullptr; } - auto row = results.begin(); DBbotspells_Struct spell_set; - spell_set.parent_list = std::stoul(row[1]); - spell_set.attack_proc = static_cast(std::stoul(row[2])); - spell_set.proc_chance = static_cast(std::stoul(row[3])); - spell_set.range_proc = static_cast(std::stoul(row[4])); - spell_set.rproc_chance = static_cast(std::stoi(row[5])); - spell_set.defensive_proc = static_cast(std::stoul(row[6])); - spell_set.dproc_chance = static_cast(std::stoi(row[7])); - spell_set.fail_recast = std::stoul(row[8]); - spell_set.engaged_no_sp_recast_min = std::stoul(row[9]); - spell_set.engaged_no_sp_recast_max = std::stoul(row[10]); - spell_set.engaged_beneficial_self_chance = static_cast(std::stoul(row[11])); - spell_set.engaged_beneficial_other_chance = static_cast(std::stoul(row[12])); - spell_set.engaged_detrimental_chance = static_cast(std::stoul(row[13])); - spell_set.pursue_no_sp_recast_min = std::stoul(row[14]); - spell_set.pursue_no_sp_recast_max = std::stoul(row[15]); - spell_set.pursue_detrimental_chance = static_cast(std::stoul(row[16])); - spell_set.idle_no_sp_recast_min = std::stoul(row[17]); - spell_set.idle_no_sp_recast_max = std::stoul(row[18]); - spell_set.idle_beneficial_chance = static_cast(std::stoul(row[19])); + spell_set.parent_list = n.parent_list; + spell_set.attack_proc = n.attack_proc; + spell_set.proc_chance = n.proc_chance; + spell_set.range_proc = n.range_proc; + spell_set.rproc_chance = n.rproc_chance; + spell_set.defensive_proc = n.defensive_proc; + spell_set.dproc_chance = n.dproc_chance; + spell_set.fail_recast = n.fail_recast; + spell_set.engaged_no_sp_recast_min = n.engaged_no_sp_recast_min; + spell_set.engaged_no_sp_recast_max = n.engaged_no_sp_recast_max; + spell_set.engaged_beneficial_self_chance = n.engaged_b_self_chance; + spell_set.engaged_beneficial_other_chance = n.engaged_b_other_chance; + spell_set.engaged_detrimental_chance = n.engaged_d_chance; + spell_set.pursue_no_sp_recast_min = n.pursue_no_sp_recast_min; + spell_set.pursue_no_sp_recast_max = n.pursue_no_sp_recast_max; + spell_set.pursue_detrimental_chance = n.pursue_d_chance; + spell_set.idle_no_sp_recast_min = n.idle_no_sp_recast_min; + spell_set.idle_no_sp_recast_max = n.idle_no_sp_recast_max; + spell_set.idle_beneficial_chance = n.idle_b_chance; - // pulling fixed values from an auto-increment field is dangerous... - query = fmt::format( - "SELECT spellid, type, minlevel, maxlevel, " - "manacost, recast_delay, priority, min_hp, max_hp, resist_adjust, " - "bucket_name, bucket_value, bucket_comparison " - "FROM bot_spells_entries " - "WHERE npc_spells_id = {} ORDER BY minlevel", - iDBSpellsID + auto bse = BotSpellsEntriesRepository::GetWhere( + content_db, + fmt::format( + "npc_spells_id = {}", + bot_spell_id + ) ); - results = QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { + if (bse.empty()) { return nullptr; } - for (auto row : results) { + for (const auto& e : bse) { DBbotspells_entries_Struct entry; - auto spell_id = std::stoi(row[0]); - entry.spellid = spell_id; - entry.type = std::stoul(row[1]); - entry.minlevel = static_cast(std::stoul(row[2])); - entry.maxlevel = static_cast(std::stoul(row[3])); - entry.manacost = static_cast(std::stoi(row[4])); - entry.recast_delay = std::stoi(row[5]); - entry.priority = static_cast(std::stoi(row[6])); - entry.min_hp = static_cast(std::stoi(row[7])); - entry.max_hp = static_cast(std::stoi(row[8])); - entry.resist_adjust = static_cast(std::stoi(row[9])); - entry.bucket_name = row[10]; - entry.bucket_value = row[11]; - entry.bucket_comparison = static_cast(std::stoul(row[12])); + entry.spellid = e.spellid; + entry.type = e.type; + entry.minlevel = e.minlevel; + entry.maxlevel = e.maxlevel; + entry.manacost = e.manacost; + entry.recast_delay = e.recast_delay; + entry.priority = e.priority; + entry.min_hp = e.min_hp; + entry.max_hp = e.max_hp; + entry.resist_adjust = e.resist_adjust; + entry.bucket_name = e.bucket_name; + entry.bucket_value = e.bucket_value; + entry.bucket_comparison = e.bucket_comparison; // some spell types don't make much since to be priority 0, so fix that if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) { entry.priority = 1; } - if (row[9]) { - entry.resist_adjust = static_cast(std::stoi(row[9])); - } else if (IsValidSpell(spell_id)) { - entry.resist_adjust = spells[spell_id].resist_difficulty; + if (e.resist_adjust) { + entry.resist_adjust = e.resist_adjust; + } else if (IsValidSpell(e.spellid)) { + entry.resist_adjust = spells[e.spellid].resist_difficulty; } spell_set.entries.push_back(entry); } - Bot_Spells_Cache.insert(std::make_pair(iDBSpellsID, spell_set)); + bot_spells_cache.insert(std::make_pair(bot_spell_id, spell_set)); - return &Bot_Spells_Cache[iDBSpellsID]; + return &bot_spells_cache[bot_spell_id]; } return nullptr; diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp index 9e512050f..9175732dc 100644 --- a/zone/lua_bot.cpp +++ b/zone/lua_bot.cpp @@ -109,6 +109,26 @@ void Lua_Bot::SetExpansionBitmask(int expansion_bitmask, bool save) { self->SetExpansionBitmask(expansion_bitmask, save); } +bool Lua_Bot::ReloadBotDataBuckets() { + Lua_Safe_Call_Bool(); + return self->GetBotDataBuckets(); +} + +bool Lua_Bot::ReloadBotOwnerDataBuckets() { + Lua_Safe_Call_Bool(); + return self->GetBotOwnerDataBuckets(); +} + +bool Lua_Bot::ReloadBotSpells() { + Lua_Safe_Call_Bool(); + return self->AI_AddBotSpells(self->GetBotSpellID()); +} + +void Lua_Bot::ReloadBotSpellSettings() { + Lua_Safe_Call_Void(); + self->LoadBotSpellSettings(); +} + bool Lua_Bot::HasBotSpellEntry(uint16 spellid) { Lua_Safe_Call_Bool(); return self->HasBotSpellEntry(spellid); @@ -117,27 +137,31 @@ bool Lua_Bot::HasBotSpellEntry(uint16 spellid) { luabind::scope lua_register_bot() { return luabind::class_("Bot") .def(luabind::constructor<>()) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem) - .def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem) - .def("CountBotItem", (uint32(Lua_Bot::*)(uint32)) & Lua_Bot::CountBotItem) - .def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16)) & Lua_Bot::GetBotItem) - .def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16)) & Lua_Bot::GetBotItemIDBySlot) - .def("GetExpansionBitmask", (int(Lua_Bot::*)(void)) & Lua_Bot::GetExpansionBitmask) - .def("GetOwner", (Lua_Mob(Lua_Bot::*)(void)) & Lua_Bot::GetOwner) - .def("HasBotItem", (bool(Lua_Bot::*)(uint32)) & Lua_Bot::HasBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem) + .def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem) + .def("CountBotItem", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountBotItem) + .def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItem) + .def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItemIDBySlot) + .def("GetExpansionBitmask", (int(Lua_Bot::*)(void))&Lua_Bot::GetExpansionBitmask) + .def("GetOwner", (Lua_Mob(Lua_Bot::*)(void))&Lua_Bot::GetOwner) + .def("HasBotItem", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem) .def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16)) & Lua_Bot::HasBotSpellEntry) - .def("OwnerMessage", (void(Lua_Bot::*)(std::string)) & Lua_Bot::OwnerMessage) - .def("RemoveBotItem", (void(Lua_Bot::*)(uint32)) & Lua_Bot::RemoveBotItem) - .def("SetExpansionBitmask", (void(Lua_Bot::*)(int)) & Lua_Bot::SetExpansionBitmask) - .def("SetExpansionBitmask", (void(Lua_Bot::*)(int, bool)) & Lua_Bot::SetExpansionBitmask) - .def("SignalBot", (void(Lua_Bot::*)(int)) & Lua_Bot::SignalBot); + .def("OwnerMessage", (void(Lua_Bot::*)(std::string))&Lua_Bot::OwnerMessage) + .def("ReloadBotDataBuckets", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotDataBuckets) + .def("ReloadBotOwnerDataBuckets", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotOwnerDataBuckets) + .def("ReloadBotSpells", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotSpells) + .def("ReloadBotSpellSettings", (void(Lua_Bot::*)(void))&Lua_Bot::ReloadBotSpellSettings) + .def("RemoveBotItem", (void(Lua_Bot::*)(uint32))&Lua_Bot::RemoveBotItem) + .def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask) + .def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask) + .def("SignalBot", (void(Lua_Bot::*)(int))&Lua_Bot::SignalBot); } #endif diff --git a/zone/lua_bot.h b/zone/lua_bot.h index 7f457cda9..f05e7f58f 100644 --- a/zone/lua_bot.h +++ b/zone/lua_bot.h @@ -43,6 +43,10 @@ public: Lua_Mob GetOwner(); bool HasBotItem(uint32 item_id); void OwnerMessage(std::string message); + bool ReloadBotDataBuckets(); + bool ReloadBotOwnerDataBuckets(); + bool ReloadBotSpells(); + void ReloadBotSpellSettings(); void RemoveBotItem(uint32 item_id); void SetExpansionBitmask(int expansion_bitmask); void SetExpansionBitmask(int expansion_bitmask, bool save); diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp index 2bf450100..8b513cea4 100644 --- a/zone/perl_bot.cpp +++ b/zone/perl_bot.cpp @@ -106,6 +106,26 @@ void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask, bool save) self->SetExpansionBitmask(expansion_bitmask, save); } +bool Perl_Bot_ReloadBotDataBuckets(Bot* self) +{ + return self->GetBotDataBuckets(); +} + +bool Perl_Bot_ReloadBotOwnerDataBuckets(Bot* self) +{ + return self->GetBotOwnerDataBuckets(); +} + +bool Perl_Bot_ReloadBotSpells(Bot* self) +{ + return self->AI_AddBotSpells(self->GetBotSpellID()); +} + +void Perl_Bot_ReloadBotSpellSettings(Bot* self) +{ + self->LoadBotSpellSettings(); +} + bool Perl_Bot_HasBotSpellEntry(Bot* self, uint16 spellid) { return self->HasBotSpellEntry(spellid); @@ -134,6 +154,10 @@ void perl_register_bot() package.add("HasBotItem", &Perl_Bot_HasBotItem); package.add("HasBotSpellEntry", &Perl_Bot_HasBotSpellEntry); package.add("OwnerMessage", &Perl_Bot_OwnerMessage); + package.add("ReloadBotDataBuckets", &Perl_Bot_ReloadBotDataBuckets); + package.add("ReloadBotOwnerDataBuckets", &Perl_Bot_ReloadBotOwnerDataBuckets); + package.add("ReloadBotSpells", &Perl_Bot_ReloadBotSpells); + package.add("ReloadBotSpellSettings", &Perl_Bot_ReloadBotSpellSettings); package.add("RemoveBotItem", &Perl_Bot_RemoveBotItem); package.add("SetExpansionBitmask", (void(*)(Bot*, int))&Perl_Bot_SetExpansionBitmask); package.add("SetExpansionBitmask", (void(*)(Bot*, int, bool))&Perl_Bot_SetExpansionBitmask); diff --git a/zone/zonedb.h b/zone/zonedb.h index 2fc07c5a2..94e55e92d 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -533,8 +533,8 @@ public: const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false); /*Bots */ - DBbotspells_Struct* GetBotSpells(uint32 iDBSpellsID); - void ClearBotSpells() { Bot_Spells_Cache.clear(); Bot_Spells_LoadTried.clear(); } + DBbotspells_Struct* GetBotSpells(uint32 bot_spell_id); + void ClearBotSpells() { bot_spells_cache.clear(); bot_spells_loadtried.clear(); } /* Mercs */ const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel); @@ -637,8 +637,9 @@ protected: std::unordered_set npc_spells_loadtried; DBnpcspellseffects_Struct** npc_spellseffects_cache; bool* npc_spellseffects_loadtried; - std::unordered_map Bot_Spells_Cache; - std::unordered_set Bot_Spells_LoadTried; + std::unordered_map bot_spells_cache; + std::unordered_set bot_spells_loadtried; + }; extern ZoneDatabase database;