From e2201631531f2c7f89c74a228e8e20d93cfd324a Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:09:32 -0600 Subject: [PATCH] Implement blocked_buffs and blocked_pet_buffs --- .../database_update_manifest_bots.cpp | 19 + .../base/base_bot_blocked_buffs_repository.h | 416 ++++++++++++++ .../bot_blocked_buffs_repository.h | 50 ++ common/ruletypes.h | 1 + common/version.h | 2 +- zone/bot.cpp | 189 ++++++- zone/bot.h | 9 + zone/bot_command.cpp | 3 + zone/bot_command.h | 2 + zone/bot_commands/blocked_buffs.cpp | 518 ++++++++++++++++++ zone/bot_commands/cast.cpp | 2 +- zone/bot_database.cpp | 108 ++++ zone/bot_database.h | 3 + zone/bot_structs.h | 7 + zone/spells.cpp | 5 +- 15 files changed, 1323 insertions(+), 11 deletions(-) create mode 100644 common/repositories/base/base_bot_blocked_buffs_repository.h create mode 100644 common/repositories/bot_blocked_buffs_repository.h create mode 100644 zone/bot_commands/blocked_buffs.cpp diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index fb21a4b67..1b9b2e439 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -1178,6 +1178,25 @@ WHERE NOT EXISTS (SELECT * FROM spells_new WHERE bot_spells_entries.spell_id = spells_new.id); +)" + }, +ManifestEntry{ + .version = 9052, + .description = "2024_12_15_bot_blocked_buffs.sql", + .check = "SHOW TABLES LIKE 'bot_blocked_buffs'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `bot_blocked_buffs` ( + `bot_id` INT(11) UNSIGNED NOT NULL, + `spell_id` INT(11) UNSIGNED NOT NULL, + `blocked` TINYINT(4) UNSIGNED NULL DEFAULT '0', + `blocked_pet` TINYINT(4) UNSIGNED NULL DEFAULT '0', + PRIMARY KEY (`bot_id`, `spell_id`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +; )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/repositories/base/base_bot_blocked_buffs_repository.h b/common/repositories/base/base_bot_blocked_buffs_repository.h new file mode 100644 index 000000000..77b0ee2cd --- /dev/null +++ b/common/repositories/base/base_bot_blocked_buffs_repository.h @@ -0,0 +1,416 @@ +/** + * 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://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H +#define EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseBotBlockedBuffsRepository { +public: + struct BotBlockedBuffs { + uint32_t bot_id; + uint32_t spell_id; + uint8_t blocked; + uint8_t blocked_pet; + }; + + static std::string PrimaryKey() + { + return std::string("bot_id"); + } + + static std::vector Columns() + { + return { + "bot_id", + "spell_id", + "blocked", + "blocked_pet", + }; + } + + static std::vector SelectColumns() + { + return { + "bot_id", + "spell_id", + "blocked", + "blocked_pet", + }; + } + + 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_blocked_buffs"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static BotBlockedBuffs NewEntity() + { + BotBlockedBuffs e{}; + + e.bot_id = 0; + e.spell_id = 0; + e.blocked = 0; + e.blocked_pet = 0; + + return e; + } + + static BotBlockedBuffs GetBotBlockedBuffs( + const std::vector &bot_blocked_buffss, + int bot_blocked_buffs_id + ) + { + for (auto &bot_blocked_buffs : bot_blocked_buffss) { + if (bot_blocked_buffs.bot_id == bot_blocked_buffs_id) { + return bot_blocked_buffs; + } + } + + return NewEntity(); + } + + static BotBlockedBuffs FindOne( + Database& db, + int bot_blocked_buffs_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + bot_blocked_buffs_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + BotBlockedBuffs e{}; + + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int bot_blocked_buffs_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + bot_blocked_buffs_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const BotBlockedBuffs &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[1] + " = " + std::to_string(e.spell_id)); + v.push_back(columns[2] + " = " + std::to_string(e.blocked)); + v.push_back(columns[3] + " = " + std::to_string(e.blocked_pet)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.bot_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static BotBlockedBuffs InsertOne( + Database& db, + BotBlockedBuffs e + ) + { + std::vector v; + + 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.blocked)); + v.push_back(std::to_string(e.blocked_pet)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.bot_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.bot_id)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.blocked)); + v.push_back(std::to_string(e.blocked_pet)); + + 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) { + BotBlockedBuffs e{}; + + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; + + 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) { + BotBlockedBuffs e{}; + + e.bot_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.spell_id = row[1] ? static_cast(atoi(row[1])) : 0; + e.blocked = row[2] ? static_cast(atoi(row[2])) : 0; + e.blocked_pet = row[3] ? static_cast(atoi(row[3])) : 0; + + 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); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const BotBlockedBuffs &e + ) + { + std::vector v; + + 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.blocked)); + v.push_back(std::to_string(e.blocked_pet)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + 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.blocked)); + v.push_back(std::to_string(e.blocked_pet)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_BOT_BLOCKED_BUFFS_REPOSITORY_H diff --git a/common/repositories/bot_blocked_buffs_repository.h b/common/repositories/bot_blocked_buffs_repository.h new file mode 100644 index 000000000..be6466cbb --- /dev/null +++ b/common/repositories/bot_blocked_buffs_repository.h @@ -0,0 +1,50 @@ +#ifndef EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H +#define EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_bot_blocked_buffs_repository.h" + +class BotBlockedBuffsRepository: public BaseBotBlockedBuffsRepository { +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 + * + * BotBlockedBuffsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * BotBlockedBuffsRepository::GetWhereNeverExpires() + * BotBlockedBuffsRepository::GetWhereXAndY() + * BotBlockedBuffsRepository::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 + +}; + +#endif //EQEMU_BOT_BLOCKED_BUFFS_REPOSITORY_H diff --git a/common/ruletypes.h b/common/ruletypes.h index 832878b40..86974af58 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -891,6 +891,7 @@ RULE_BOOL(Bots, AllowCommandedLull, true, "If enabled bots can be commanded to l RULE_INT(Bots, CampTimer, 25, "Number of seconds after /camp has begun before bots camp out.") RULE_BOOL(Bots, SendClassRaceOnHelp, true, "If enabled a reminder of how to check class/race IDs will be sent when using compatible commands.") RULE_BOOL(Bots, AllowCrossGroupRaidAssist, true, "If enabled bots will autodefend group or raid members set as main assist.") +RULE_BOOL(Bots, AllowBotBlockedBuffs, true, "If enabled, you can create blocked buffs for each bot and for their pets.") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/common/version.h b/common/version.h index d115f814b..731ffab16 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9286 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9051 //TODO bot rewrite +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9052 //TODO bot rewrite #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index d791020ad..70a57cf6a 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -108,6 +108,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm GenerateAppearance(); GenerateBaseStats(); bot_timers.clear(); + bot_blocked_buffs.clear(); // Calculate HitPoints Last As It Uses Base Stats current_hp = GenerateBaseHitPoints(); @@ -247,13 +248,16 @@ Bot::Bot( GenerateBaseStats(); bot_timers.clear(); - database.botdb.LoadTimers(this); LoadDefaultBotSettings(); - database.botdb.LoadBotSettings(this); + if (RuleB(Bots, AllowBotBlockedBuffs)) { + bot_blocked_buffs.clear(); + database.botdb.LoadBotBlockedBuffs(this); + } + LoadAAs(); if (database.botdb.LoadBuffs(this)) { @@ -1374,6 +1378,10 @@ bool Bot::Save() database.botdb.SaveStance(this); database.botdb.SaveBotSettings(this); + if (RuleB(Bots, AllowBotBlockedBuffs)) { + database.botdb.SaveBotBlockedBuffs(this); + } + if (!SavePet()) bot_owner->Message(Chat::White, "Failed to save pet for '%s'", GetCleanName()); @@ -1439,6 +1447,10 @@ bool Bot::DeleteBot() return false; } + if (!database.botdb.DeleteBotBlockedBuffs(GetBotID())) { + return false; + } + if (!database.botdb.DeleteBot(GetBotID())) { return false; } @@ -9573,11 +9585,22 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spellType, bool doPrechec return false; } - if (IsBeneficialSpell(spell_id) && tar->IsBlockedBuff(spell_id)) { - LogBotPreChecks("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + if (RuleB(Spells, EnableBlockedBuffs) && IsBeneficialSpell(spell_id) && tar->IsClient() && tar->IsBlockedBuff(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme return false; } + if (RuleB(Bots, AllowBotBlockedBuffs) && IsBeneficialSpell(spell_id)) { + if (tar->IsBot() && tar->IsBlockedBuff(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsBlockedBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + else if (tar->IsPet() && tar->GetOwner() && tar->GetOwner()->IsBot() && tar->GetOwner()->IsBlockedPetBuff(spell_id)) { + LogBotPreChecksDetail("{} says, 'Cancelling cast of {} on {} due to IsBlockedPetBuff.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme + return false; + } + } + LogBotPreChecksDetail("{} says, 'Doing CanCastSpellType checks of {} on {}.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName()); //deleteme if (!CanCastSpellType(spellType, spell_id, tar)) { return false; @@ -11887,7 +11910,7 @@ bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell return true; } - break; +break; case CommandedSubTypes::InvisAnimals: if (IsEffectInSpell(spell_id, SE_InvisVsAnimals) || IsEffectInSpell(spell_id, SE_ImprovedInvisAnimals)) { return true; @@ -11898,7 +11921,7 @@ bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell if ( (IsEffectInSpell(spell_id, SE_ModelSize) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ModelSize), GetLevel()) < 100) || (IsEffectInSpell(spell_id, SE_ChangeHeight) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ChangeHeight), GetLevel()) < 100) - ) { + ) { return true; } @@ -11907,7 +11930,7 @@ bool Bot::IsValidSpellTypeSubType(uint16 spellType, uint16 subType, uint16 spell if ( (IsEffectInSpell(spell_id, SE_ModelSize) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ModelSize), GetLevel()) > 100) || (IsEffectInSpell(spell_id, SE_ChangeHeight) && CalcSpellEffectValue(spell_id, GetSpellEffectIndex(spell_id, SE_ChangeHeight), GetLevel()) > 100) - ) { + ) { return true; } @@ -11945,7 +11968,7 @@ uint16 Bot::GetSpellByAA(int id, AA::Rank*& rank) { if (!points || !rank) { LogTestDebug("{}: {} says, 'No {} found'", __LINE__, GetCleanName(), (!points ? "points" : "rank")); //deleteme return spell_id; - } + } spell_id = rank->spell; @@ -11953,3 +11976,153 @@ uint16 Bot::GetSpellByAA(int id, AA::Rank*& rank) { return spell_id; } + +void Bot::SetBotBlockedBuff(uint16 spell_id, bool block) { + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set blocked buff."); + + return; + } + + if (!bot_blocked_buffs.empty()) { + bool found = false; + + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + bot_blocked_buffs[i].blocked = block; + found = true; + } + + if (!found) { + BotBlockedBuffs_Struct t; + + t.spell_id = spell_id; + t.blocked = block; + + bot_blocked_buffs.push_back(t); + } + } + else { + + BotBlockedBuffs_Struct t; + + t.spell_id = spell_id; + t.blocked = block; + + bot_blocked_buffs.push_back(t); + } + + CleanBotBlockedBuffs(); +} + +bool Bot::IsBlockedBuff(int32 spell_id) +{ + bool result = false; + + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to get blocked buff."); + + return result; + } + + CleanBotBlockedBuffs(); + + if (!bot_blocked_buffs.empty()) { + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + return bot_blocked_buffs[i].blocked; + } + } + + return result; +} + +void Bot::SetBotBlockedPetBuff(uint16 spell_id, bool block) { + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set blocked pet buff."); + + return; + } + + if (!bot_blocked_buffs.empty()) { + bool found = false; + + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + bot_blocked_buffs[i].blocked_pet = block; + found = true; + } + + if (!found) { + BotBlockedBuffs_Struct t; + + t.spell_id = spell_id; + t.blocked_pet = block; + + bot_blocked_buffs.push_back(t); + } + } + else { + BotBlockedBuffs_Struct t; + + t.spell_id = spell_id; + t.blocked_pet = block; + + bot_blocked_buffs.push_back(t); + } + + CleanBotBlockedBuffs(); +} + +bool Bot::IsBlockedPetBuff(int32 spell_id) +{ + bool result = false; + + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to get blocked pet buff."); + + return result; + } + + CleanBotBlockedBuffs(); + + if (!bot_blocked_buffs.empty()) { + for (int i = 0; i < bot_blocked_buffs.size(); i++) { + if (bot_blocked_buffs[i].spell_id != spell_id) { + continue; + } + + return bot_blocked_buffs[i].blocked_pet; + } + } + + return result; +} + +void Bot::CleanBotBlockedBuffs() +{ + if (!bot_blocked_buffs.empty()) { + int current = 0; + int end = bot_blocked_buffs.size(); + + while (current < end) { + if (!IsValidSpell(bot_blocked_buffs[current].spell_id) || (bot_blocked_buffs[current].blocked == 0 && bot_blocked_buffs[current].blocked_pet == 0)) { + bot_blocked_buffs.erase(bot_blocked_buffs.begin() + current); + } + else { + current++; + } + + end = bot_blocked_buffs.size(); + } + } +} diff --git a/zone/bot.h b/zone/bot.h index 457f7eebf..cffadc4d3 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -478,6 +478,12 @@ public: void SetBotSpellRecastTimer(uint16 spellType, Mob* spelltar, bool preCast = false); BotSpell GetSpellByHealType(uint16 spellType, Mob* tar); uint16 GetSpellByAA(int id, AA::Rank* &rank); + void CleanBotBlockedBuffs(); + void ClearBotBlockedBuffs() { bot_blocked_buffs.clear(); } + bool IsBlockedBuff(int32 spell_id) override; //TODO bot rewrite - fix these to call from mob.h + bool IsBlockedPetBuff(int32 spell_id) override; //TODO bot rewrite - fix these to call from mob.h + void SetBotBlockedBuff(uint16 spell_id, bool block); + void SetBotBlockedPetBuff(uint16 spell_id, bool block); std::string GetBotSpellCategoryName(uint8 setting_type); std::string GetBotSettingCategoryName(uint8 setting_type); @@ -882,6 +888,8 @@ public: bool DeleteBot(); std::vector GetBotTimers() { return bot_timers; } void SetBotTimers(std::vector timers) { bot_timers = timers; } + std::vector GetBotBlockedBuffs() { return bot_blocked_buffs; } + void SetBotBlockedBuffs(std::vector blockedBuff) { bot_blocked_buffs = blockedBuff; } uint32 GetLastZoneID() const { return _lastZoneId; } int32 GetBaseAC() const { return _baseAC; } int32 GetBaseATK() const { return _baseATK; } @@ -1010,6 +1018,7 @@ protected: std::vector AIBot_spells; std::vector AIBot_spells_enforced; std::vector bot_timers; + std::vector bot_blocked_buffs; private: // Class Members diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index a2a25a71f..6e86ecdd4 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1251,6 +1251,8 @@ int bot_command_init(void) bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", AccountStatus::Player, bot_command_apply_poison) || bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) || bot_command_add("behindmob", "Toggles whether or not your bot tries to stay behind a mob", AccountStatus::Player, bot_command_behind_mob) || + bot_command_add("blockedbuffs", "Set, view and clear blocked buffs for the selected bot(s)", AccountStatus::Player, bot_command_blocked_buffs) || + bot_command_add("blockedpetbuffs", "Set, view and clear blocked pet buffs for the selected bot(s)", AccountStatus::Player, bot_command_blocked_pet_buffs) || bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) || bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) || bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_command_beard_color) || @@ -2216,6 +2218,7 @@ void SendSpellTypeWindow(Client* c, const Seperator* sep) { #include "bot_commands/apply_potion.cpp" #include "bot_commands/attack.cpp" #include "bot_commands/behind_mob.cpp" +#include "bot_commands/blocked_buffs.cpp" #include "bot_commands/bot.cpp" #include "bot_commands/bot_settings.cpp" #include "bot_commands/cast.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index d6644bbf6..875c6f3ac 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -1669,6 +1669,8 @@ void bot_command_apply_poison(Client *c, const Seperator *sep); void bot_command_apply_potion(Client* c, const Seperator* sep); void bot_command_attack(Client *c, const Seperator *sep); void bot_command_behind_mob(Client* c, const Seperator* sep); +void bot_command_blocked_buffs(Client* c, const Seperator* sep); +void bot_command_blocked_pet_buffs(Client* c, const Seperator* sep); void bot_command_bot(Client *c, const Seperator *sep); void bot_command_bot_settings(Client* c, const Seperator* sep); void bot_command_cast(Client* c, const Seperator* sep); diff --git a/zone/bot_commands/blocked_buffs.cpp b/zone/bot_commands/blocked_buffs.cpp new file mode 100644 index 000000000..a89c75e98 --- /dev/null +++ b/zone/bot_commands/blocked_buffs.cpp @@ -0,0 +1,518 @@ +#include "../bot_command.h" + +void bot_command_blocked_buffs(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, AllowBotBlockedBuffs)) { + c->Message(Chat::Yellow, "This command is disabled."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Allows you to set, view and wipe blocked buffs for the selected bots" + }; + + std::vector notes = + { + "- You can 'set' spells to be blocked, 'remove' spells from the blocked list, 'list' the current blocked spells or 'wipe' the entire list." + }; + + std::vector example_format = + { + fmt::format( + "{} [add [ID] | remove [ID] | list | wipe] [actionable, default: target]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To add Courage (Spell ID #202) to the targeted bot's blocked list:", + fmt::format( + "{} add 202", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ) + }; + std::vector examples_two = + { + "To view the targeted bot's blocked buff list:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To wipe all Warriors bots' blocked buff list:", + fmt::format( + "{} wipe byclass {}", + sep->arg[0], + Class::Warrior + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + c->Message( + Chat::Yellow, + fmt::format( + "You can also control bot buffs ({}).", + Saylink::Silent("^blockedbuffs help", "^blockedbuffs") + ).c_str() + ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool add = false; + bool remove = false; + bool list = false; + bool wipe = false; + uint16 spell_id; + + //AA help + if (!arg1.compare("add")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + add = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("remove")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + remove = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("list")) { + list = true; + } + else if (!arg1.compare("wipe")) { + wipe = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "target"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + bool isSuccess = false; + uint16 successCount = 0; + Bot* firstFound = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(c)) { + continue; + } + + if (!firstFound) { + firstFound = bot_iter; + } + + if (add) { + bot_iter->SetBotBlockedBuff(spell_id, true); + } + else if (remove) { + bot_iter->SetBotBlockedBuff(spell_id, false); + } + else if (list) { + std::vector blockedBuffs = bot_iter->GetBotBlockedBuffs(); + bool found = false; + + if (!blockedBuffs.empty()) { + for (auto& blocked_buff : blockedBuffs) { + if (blocked_buff.blocked == 1 && IsValidSpell(blocked_buff.spell_id)) { + found = true; + c->Message( + Chat::Yellow, + fmt::format( + "{} says, '{} [#{}] is currently blocked.'", + bot_iter->GetCleanName(), + spells[blocked_buff.spell_id].name, + blocked_buff.spell_id + ).c_str() + ); + } + } + } + + if (!found) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am not currently blocking any buffs.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + } + else if (wipe) { + bot_iter->ClearBotBlockedBuffs(); + + c->Message( + Chat::Yellow, + fmt::format( + "{} says, I have wiped my blocked buffs list.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + + isSuccess = true; + ++successCount; + } + + if (!isSuccess) { + c->Message(Chat::Yellow, "No bots were selected."); + } + else { + if (add || remove) { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} {} blocking {} [#{}]", + ((successCount == 1 && firstFound) ? firstFound->GetCleanName() : (fmt::format("{}", successCount).c_str())), + ((successCount == 1 && firstFound) ? "is" : "of your bots"), + (add ? "now" : "no longer"), + spells[spell_id].name, + spell_id + ).c_str() + ); + } + } +} + + +void bot_command_blocked_pet_buffs(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, AllowBotBlockedBuffs)) { + c->Message(Chat::Yellow, "This command is disabled."); + + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Allows you to set, view and wipe blocked pet buffs for the selected bots" + }; + + std::vector notes = + { + "- You can 'set' spells to be blocked, 'remove' spells from the blocked list, 'list' the current blocked spells or 'wipe' the entire list.", + "- This controls whether or not any pet the selected bot(s) own will prevent certain buffs from being cast." + }; + + std::vector example_format = + { + fmt::format( + "{} [add [ID] | remove [ID] | list | wipe] [actionable, default: target]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To add Courage (Spell ID #202) to the targeted bot's blocked list:", + fmt::format( + "{} add 202", + sep->arg[0], + c->GetSpellTypeShortNameByID(BotSpellTypes::Nuke) + ) + }; + std::vector examples_two = + { + "To view the targeted bot's blocked buff list:", + fmt::format( + "{} list", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To wipe all Warriors bots' blocked buff list:", + fmt::format( + "{} wipe byclass {}", + sep->arg[0], + Class::Warrior + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + c->Message( + Chat::Yellow, + fmt::format( + "You can also control pet buffs ({}).", + Saylink::Silent("^blockedpetbuffs help", "^blockedpetbuffs") + ).c_str() + ); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + + return; + } + + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + int ab_arg = 2; + bool add = false; + bool remove = false; + bool list = false; + bool wipe = false; + uint16 spell_id; + + //AA help + if (!arg1.compare("add")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + add = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("remove")) { + if (!sep->IsNumber(2) || !IsValidSpell(atoi(sep->arg[2]))) { + c->Message(Chat::Yellow, "You must enter a valid spell ID."); + return; + } + + remove = true; + spell_id = atoi(sep->arg[2]); + ++ab_arg; + } + else if (!arg1.compare("list")) { + list = true; + } + else if (!arg1.compare("wipe")) { + wipe = true; + } + else { + c->Message( + Chat::Yellow, + fmt::format( + "Incorrect argument, use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "target"; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + + if (ActionableBots::PopulateSBL(c, actionableArg, sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + bool isSuccess = false; + uint16 successCount = 0; + Bot* firstFound = nullptr; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(c)) { + continue; + } + + if (!firstFound) { + firstFound = bot_iter; + } + + if (add) { + bot_iter->SetBotBlockedPetBuff(spell_id, true); + } + else if (remove) { + bot_iter->SetBotBlockedPetBuff(spell_id, false); + } + else if (list) { + std::vector blockedBuffs = bot_iter->GetBotBlockedBuffs(); + bool found = false; + + if (!blockedBuffs.empty()) { + for (auto& blocked_buff : blockedBuffs) { + if (blocked_buff.blocked_pet == 1 && IsValidSpell(blocked_buff.spell_id)) { + found = true; + c->Message( + Chat::Yellow, + fmt::format( + "{} says, '{} [#{}] is currently blocked for my pet.'", + bot_iter->GetCleanName(), + spells[blocked_buff.spell_id].name, + blocked_buff.spell_id + ).c_str() + ); + } + } + } + + if (!found) { + c->Message( + Chat::Yellow, + fmt::format( + "{} says, 'I am not currently blocking any pet buffs.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + } + else if (wipe) { + bot_iter->ClearBotBlockedBuffs(); + + c->Message( + Chat::Yellow, + fmt::format( + "{} says, I have wiped my blocked buffs list.'", + bot_iter->GetCleanName() + ).c_str() + ); + } + + isSuccess = true; + ++successCount; + } + + if (!isSuccess) { + c->Message(Chat::Yellow, "No bots were selected."); + } + else { + if (add || remove) { + c->Message( + Chat::Yellow, + fmt::format( + "{} {} {} blocking {} [#{}] on their pet.", + ((successCount == 1 && firstFound) ? firstFound->GetCleanName() : (fmt::format("{}", successCount).c_str())), + ((successCount == 1 && firstFound) ? "is" : "of your bots"), + (add ? "now" : "no longer"), + spells[spell_id].name, + spell_id + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index afc923118..2b721913a 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -10,7 +10,7 @@ void bot_command_cast(Client* c, const Seperator* sep) std::vector notes = { - "- This will interrupt any spell currently being cast by bots told to use the command.", + "- This will interrupt any spell currently being cast by bots told to use the command", "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells", fmt::format( "- You can use {} aa # to cast any clickable AA or specifically {} harmtouch / {} layonhands" diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index a4d4c8385..c401604c3 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -22,6 +22,7 @@ #include "../common/strings.h" #include "../common/eqemu_logsys.h" +#include "../common/repositories/bot_blocked_buffs_repository.h" #include "../common/repositories/bot_buffs_repository.h" #include "../common/repositories/bot_create_combinations_repository.h" #include "../common/repositories/bot_data_repository.h" @@ -2403,3 +2404,110 @@ bool BotDatabase::DeleteBotSettings(const uint32 bot_id) return true; } + +bool BotDatabase::LoadBotBlockedBuffs(Bot* b) +{ + if (!b) { + return false; + } + + const auto& l = BotBlockedBuffsRepository::GetWhere( + database, + fmt::format( + "`bot_id` = {}", + b->GetBotID() + ) + ); + + std::vector v; + + BotBlockedBuffs_Struct t{ }; + + for (const auto& e : l) { + t.spell_id = e.spell_id; + t.blocked = e.blocked; + t.blocked_pet = e.blocked_pet; + + v.push_back(t); + } + + if (!v.empty()) { + b->SetBotBlockedBuffs(v); + } + + return true; +} + +bool BotDatabase::SaveBotBlockedBuffs(Bot* b) +{ + if (!b) { + return false; + } + + if (!DeleteBotBlockedBuffs(b->GetBotID())) { + return false; + } + + std::vector v = b->GetBotBlockedBuffs(); + + if (v.empty()) { + return true; + } + + std::vector l; + + if (!v.empty()) { + for (auto& blocked_buff : v) { + if (blocked_buff.blocked == 0 && blocked_buff.blocked_pet == 0) { + continue; + } + + auto e = BotBlockedBuffsRepository::BotBlockedBuffs{ + .bot_id = b->GetBotID(), + .spell_id = blocked_buff.spell_id, + .blocked = blocked_buff.blocked, + .blocked_pet = blocked_buff.blocked_pet + }; + + l.push_back(e); + } + + if (l.empty()) { + return true; + } + + BotBlockedBuffsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + b->GetBotID() + ) + ); + + const int inserted = BotBlockedBuffsRepository::InsertMany(database, l); + + if (!inserted) { + DeleteBotBlockedBuffs(b->GetBotID()); + return false; + } + } + + return true; +} + +bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id) +{ + if (!bot_id) { + return false; + } + + BotBlockedBuffsRepository::DeleteWhere( + database, + fmt::format( + "`bot_id` = {}", + bot_id + ) + ); + + return true; +} diff --git a/zone/bot_database.h b/zone/bot_database.h index 3539db5c6..61fdc6301 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -74,6 +74,9 @@ public: bool SaveTimers(Bot* b); bool DeleteTimers(const uint32 bot_id); + bool LoadBotBlockedBuffs(Bot* b); + bool SaveBotBlockedBuffs(Bot* b); + bool DeleteBotBlockedBuffs(const uint32 bot_id); /* Bot inventory functions */ bool QueryInventoryCount(const uint32 bot_id, uint32& item_count); diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 0cfc5913e..9a05a70e4 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -97,4 +97,11 @@ struct BotSpellTypeOrder { uint16 priority; }; +struct BotBlockedBuffs_Struct { + uint32_t bot_id; + uint32_t spell_id; + uint8_t blocked; + uint8_t blocked_pet; +}; + #endif // BOT_STRUCTS diff --git a/zone/spells.cpp b/zone/spells.cpp index bce05ecd1..4b5aa8fc4 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -4080,7 +4080,10 @@ bool Mob::SpellOnTarget( } // now check if the spell is allowed to land - if (RuleB(Spells, EnableBlockedBuffs)) { + if ( + (!spelltar->IsBot() && RuleB(Spells, EnableBlockedBuffs)) || + (spelltar->IsBot() && RuleB(Bots, AllowBotBlockedBuffs)) + ) { // We return true here since the caster's client should act like normal if (spelltar->IsBlockedBuff(spell_id)) { LogSpells(