diff --git a/common/emu_constants.h b/common/emu_constants.h index 7eaf3837b..29db71c64 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -567,7 +567,7 @@ enum ReloadWorld : uint8 { ForceRepop }; -enum MerchantBucketComparison : uint8 { +enum BucketComparison : uint8 { BucketEqualTo = 0, BucketNotEqualTo, BucketGreaterThanOrEqualTo, diff --git a/common/version.h b/common/version.h index 136b398d3..f59138975 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 9029 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9030 #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 2f0dc9380..bd8064e30 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -28,6 +28,7 @@ 9027|2020_03_30_bots_view_update.sql|SELECT * FROM db_version WHERE bots_version >= 9027|empty| 9028|2021_06_04_bot_create_combinations.sql|SHOW TABLES LIKE 'bot_create_combinations'|empty| 9029|2022_06_21_bot_groups_auto_spawn.sql|SHOW COLUMNS from `bot_groups` LIKE 'auto_spawm'|empty| +9030|2022_10_27_bot_data_buckets.sql|SHOW COLUMNS FROM `bot_spells_entries` LIKE 'bucket_name'|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_10_27_bot_data_buckets.sql b/utils/sql/git/bots/required/2022_10_27_bot_data_buckets.sql new file mode 100644 index 000000000..a46efa902 --- /dev/null +++ b/utils/sql/git/bots/required/2022_10_27_bot_data_buckets.sql @@ -0,0 +1,4 @@ +ALTER TABLE `bot_spells_entries` +ADD COLUMN `bucket_name` varchar(100) NOT NULL DEFAULT '' AFTER `max_hp`, +ADD COLUMN `bucket_value` varchar(100) NOT NULL DEFAULT '' AFTER `bucket_name`, +ADD COLUMN `bucket_comparison` tinyint UNSIGNED NULL DEFAULT 0 AFTER `bucket_value`; \ No newline at end of file diff --git a/zone/bot.cpp b/zone/bot.cpp index 631ebbcd1..35a04e781 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2188,7 +2188,6 @@ void Bot::AI_Bot_Start(uint32 iMoveDelay) { } if (NPCTypedata) { - AI_AddBotSpells(NPCTypedata->npc_spells_id); ProcessSpecialAbilities(NPCTypedata->special_abilities); AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id); } @@ -3345,8 +3344,8 @@ void Bot::AI_Process() TEST_COMBATANTS(); auto ExtraAttackChanceBonus = - (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + - aabonuses.ExtraAttackChance[0]); + (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + + aabonuses.ExtraAttackChance[0]); if (ExtraAttackChanceBonus) { if (p_item && p_item->GetItem()->IsType2HWeapon()) { @@ -3951,14 +3950,14 @@ bool Bot::Spawn(Client* botCharacterOwner) { void Bot::RemoveBotItemBySlot(uint16 slot_id, std::string *error_message) { if (!GetBotID()) { - return; + return; } - if (!database.botdb.DeleteItemBySlot(GetBotID(), slot_id)) { - *error_message = BotDatabase::fail::DeleteItemBySlot(); + if (!database.botdb.DeleteItemBySlot(GetBotID(), slot_id)) { + *error_message = BotDatabase::fail::DeleteItemBySlot(); } - m_inv.DeleteItem(slot_id); + m_inv.DeleteItem(slot_id); UpdateEquipmentLight(); } @@ -3982,7 +3981,7 @@ uint32 Bot::GetBotItemBySlot(uint16 slot_id) { uint32 item_id = 0; if (!GetBotID()) { - return item_id; + return item_id; } if (!database.botdb.LoadItemBySlot(GetBotID(), slot_id, item_id)) { @@ -7031,11 +7030,11 @@ int64 Bot::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) { //Crtical Hit Calculation pathway if (chance > 0 || (GetClass() == WIZARD && GetLevel() >= RuleI(Spells, WizCritLevel))) { - int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals. + int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals. //Improved Harm Touch is a guaranteed crit if you have at least one level of SCF. if (spell_id == SPELL_IMP_HARM_TOUCH && (GetAA(aaSpellCastingFury) > 0) && (GetAA(aaUnholyTouch) > 0)) - chance = 100; + chance = 100; if (spells[spell_id].override_crit_chance > 0 && chance > spells[spell_id].override_crit_chance) chance = spells[spell_id].override_crit_chance; @@ -7226,7 +7225,7 @@ int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { uint8 botlevel = GetLevel(); uint8 botclass = GetClass(); if (botlevel >= 51 && casttime >= 3000 && !spells[spell_id].good_effect && - (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD)) { + (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD)) { int level_mod = std::min(15, botlevel - 50); cast_reducer += level_mod * 3; } @@ -9235,6 +9234,8 @@ void Bot::CalcBotStats(bool showtext) { CalcBonuses(); + GetBotOwnerDataBuckets(); + GetBotDataBuckets(); AI_AddBotSpells(GetBotSpellID()); if(showtext) { @@ -10322,6 +10323,80 @@ void Bot::SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leade ); } +bool Bot::GetBotOwnerDataBuckets() +{ + auto bot_owner = GetBotOwner(); + if (!bot_owner) { + return false; + } + + 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()) { + return false; + } + + for (auto row : results) { + bot_data_buckets.insert(std::pair(row[0], row[1])); + } + + return true; +} + +bool Bot::GetBotDataBuckets() +{ + 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()) { + return false; + } + + for (auto row : results) { + bot_data_buckets.insert(std::pair(row[0], row[1])); + } + + return true; +} + +bool Bot::CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison) +{ + if (!bucket_name.empty() && !bucket_value.empty()) { + auto full_name = fmt::format( + "{}-{}", + GetBucketKey(), + bucket_name + ); + + auto player_value = bot_data_buckets[full_name]; + if (player_value.empty() && GetBotOwner()) { + full_name = fmt::format( + "{}-{}", + GetBotOwner()->GetBucketKey(), + bucket_name + ); + + player_value = bot_data_buckets[full_name]; + if (player_value.empty()) { + return false; + } + } + + if (zone->CheckDataBucket(bucket_comparison, bucket_value, player_value)) { + return true; + } + } + + return false; +} + 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 650c2e9a0..50461ea68 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -308,7 +308,19 @@ public: void DoEnduranceUpkeep(); //does the endurance upkeep bool AI_AddBotSpells(uint32 iDBSpellsID); - void AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp); + void AddSpellToBotList( + int16 iPriority, + uint16 iSpellID, + uint32 iType, + int16 iManaCost, + int32 iRecastDelay, + int16 iResistAdjust, + int8 min_hp, + int8 max_hp, + std::string bucket_name, + std::string bucket_value, + uint8 bucket_comparison + ); void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); // AI Methods virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); @@ -349,6 +361,10 @@ public: virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot); virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); + bool GetBotOwnerDataBuckets(); + bool GetBotDataBuckets(); + bool CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison); + // Bot Equipment & Inventory Class Methods void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true); void EquipBot(std::string* error_message); @@ -692,6 +708,7 @@ private: eStandingPetOrder m_previous_pet_order; BotCastingRoles m_CastingRoles; + std::map bot_data_buckets; std::shared_ptr m_member_of_heal_rotation; diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index e3db3781a..53f2632ee 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -804,8 +804,9 @@ bool BotDatabase::SaveBuffs(Bot* bot_inst) return false; for (int buff_index = 0; buff_index < BUFF_COUNT; ++buff_index) { - if (bot_buffs[buff_index].spellid <= 0 || bot_buffs[buff_index].spellid == SPELL_UNKNOWN) + if (!IsValidSpell(bot_buffs[buff_index].spellid)) { continue; + } query = StringFormat( "INSERT INTO `bot_buffs` (" diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index b9a8c93ec..abca626b9 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -19,6 +19,7 @@ #ifdef BOTS #include "bot.h" +#include "../common/data_verification.h" #include "../common/strings.h" #if EQDEBUG >= 12 @@ -1108,7 +1109,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain casting_spell_AIindex = i; LogAIDetail("Bot::AIDoSpellCast: spellid = [{}], tar = [{}], mana = [{}], Name: [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name); result = Mob::CastSpell(AIBot_spells[i].spellid, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[AIBot_spells[i].spellid].cast_time, AIBot_spells[i].manacost == -2 ? 0 : mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIBot_spells[i].resist_adjust)); - + if(IsCasting() && IsSitting()) Stand(); } @@ -1604,17 +1605,22 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) { std::list result; - if(botCaster && botCaster->AI_HasSpells()) { + auto bot_owner = botCaster->GetBotOwner(); + if (!bot_owner) { + return result; + } + + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(IsEffectInSpell(botSpellList[i].spellid, spellEffect)) { + if (IsEffectInSpell(botSpellList[i].spellid, spellEffect)) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -1631,18 +1637,23 @@ std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) { std::list result; - if(botCaster && botCaster->AI_HasSpells()) { + auto bot_owner = botCaster->GetBotOwner(); + if (!bot_owner) { + return result; + } + + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(IsEffectInSpell(botSpellList[i].spellid, spellEffect)) { - if(spells[botSpellList[i].spellid].target_type == targetType) { + if (IsEffectInSpell(botSpellList[i].spellid, spellEffect)) { + if (spells[botSpellList[i].spellid].target_type == targetType) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -1660,17 +1671,22 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { std::list result; - if(botCaster && botCaster->AI_HasSpells()) { + auto bot_owner = botCaster->GetBotOwner(); + if (!bot_owner) { + return result; + } + + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(botSpellList[i].type & spellType) { + if (botSpellList[i].type & spellType) { BotSpell botSpell; botSpell.SpellId = botSpellList[i].spellid; botSpell.SpellIndex = i; @@ -1691,7 +1707,7 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -1708,8 +1724,13 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa } } - if (result.size() > 1) - result.sort([](BotSpell_wPriority& l, BotSpell_wPriority& r) { return l.Priority < r.Priority; }); + if (result.size() > 1) { + result.sort( + [](BotSpell_wPriority& l, BotSpell_wPriority& r) { + return l.Priority < r.Priority; + } + ); + } } return result; @@ -1722,17 +1743,17 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster && botCaster->AI_HasSpells()) { + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if((botSpellList[i].type & spellType) && CheckSpellRecastTimers(botCaster, i)) { + if ((botSpellList[i].type & spellType) && CheckSpellRecastTimers(botCaster, i)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -1752,12 +1773,12 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsFastHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if (IsFastHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -1777,22 +1798,25 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); std::vector botSpellList = botCaster->AIBot_spells; - for(std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsHealOverTimeSpell(botSpellListItr->SpellId)) { - + if (IsHealOverTimeSpell(botSpellListItr->SpellId)) { for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(botSpellList[i].spellid == botSpellListItr->SpellId && (botSpellList[i].type & SpellType_Heal) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + botSpellList[i].spellid == botSpellListItr->SpellId && + (botSpellList[i].type & SpellType_Heal) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -1814,21 +1838,20 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster && botCaster->AI_HasSpells()) { + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(IsCompleteHealSpell(botSpellList[i].spellid) && CheckSpellRecastTimers(botCaster, i)) { + if (IsCompleteHealSpell(botSpellList[i].spellid) && CheckSpellRecastTimers(botCaster, i)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; - break; } } @@ -1844,16 +1867,15 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if (IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; - break; } } @@ -1869,16 +1891,21 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if((IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || IsFastHealSpell(botSpellListItr->SpellId)) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + ( + IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || + IsFastHealSpell(botSpellListItr->SpellId) + ) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; - break; } } @@ -1894,16 +1921,18 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsRegularGroupHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + IsRegularGroupHealSpell(botSpellListItr->SpellId) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; - break; } } @@ -1919,22 +1948,26 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); std::vector botSpellList = botCaster->AIBot_spells; - for(std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { + if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; } - if(botSpellList[i].spellid == botSpellListItr->SpellId && (botSpellList[i].type & SpellType_Heal) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + botSpellList[i].spellid == botSpellListItr->SpellId && + (botSpellList[i].type & SpellType_Heal) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -1956,16 +1989,18 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CompleteHeal); for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsGroupCompleteHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if( + IsGroupCompleteHealSpell(botSpellListItr->SpellId) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; - break; } } @@ -1981,12 +2016,15 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_Mez); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsMezSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + IsMezSpell(botSpellListItr->SpellId) && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2006,16 +2044,19 @@ BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + IsSlowSpell(botSpellListItr->SpellId) && + spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; - break; } } @@ -2031,12 +2072,16 @@ BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_AttackSpeed); - for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { // Assuming all the spells have been loaded into this list by level and in descending order - if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ( + IsSlowSpell(botSpellListItr->SpellId) && + spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && + CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2052,7 +2097,7 @@ BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell) { Mob* result = 0; - if(botCaster && IsMezSpell(botSpell.SpellId)) { + if (botCaster && IsMezSpell(botSpell.SpellId)) { std::list npc_list; entity_list.GetNPCList(npc_list); @@ -2060,14 +2105,16 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell) { for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); ++itr) { NPC* npc = *itr; - if(DistanceSquaredNoZ(npc->GetPosition(), botCaster->GetPosition()) <= botCaster->GetActSpellRange(botSpell.SpellId, spells[botSpell.SpellId].range)) { - if(!npc->IsMezzed()) { - if(botCaster->HasGroup()) { + if (DistanceSquaredNoZ(npc->GetPosition(), botCaster->GetPosition()) <= botCaster->GetActSpellRange(botSpell.SpellId, spells[botSpell.SpellId].range)) { + if (!npc->IsMezzed()) { + if (botCaster->HasGroup()) { Group* g = botCaster->GetGroup(); - if(g) { - for(int counter = 0; counter < g->GroupCount(); counter++) { - if(npc->IsOnHatelist(g->members[counter]) && g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) { + if (g) { + for (int counter = 0; counter < g->GroupCount(); counter++) { + if ( + npc->IsOnHatelist(g->members[counter]) && + g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) { result = npc; break; } @@ -2092,7 +2139,7 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_SummonPet); std::string petType = GetBotMagicianPetType(botCaster); @@ -2117,7 +2164,7 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { std::string Bot::GetBotMagicianPetType(Bot* botCaster) { std::string result; - if(botCaster) { + if (botCaster) { if(botCaster->IsPetChooser()) { switch(botCaster->GetPetChooserID()) { case 0: @@ -2203,7 +2250,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) { + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_CurrentHP, targetType); for(std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { @@ -2229,7 +2276,7 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType result.SpellIndex = 0; result.ManaCost = 0; - if(botCaster) + if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffectAndTargetType(botCaster, SE_Stun, targetType); @@ -2331,11 +2378,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) { if(!tar || !botCaster) return result; - if(botCaster && botCaster->AI_HasSpells()) { + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -2378,11 +2425,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { bool needsPoisonResistDebuff = (tar->GetPR() + level_mod) > 100 ? true: false; bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100 ? true: false; - if(botCaster && botCaster->AI_HasSpells()) { + if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; for (int i = botSpellList.size() - 1; i >= 0; i--) { - if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -2426,7 +2473,7 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { bool isCursed = tar->FindType(SE_CurseCounter); bool isCorrupted = tar->FindType(SE_CorruptionCounter); - if(botCaster && botCaster->AI_HasSpells()) { + if (botCaster && botCaster->AI_HasSpells()) { std::list cureList = GetPrioritizedBotSpellsBySpellType(botCaster, SpellType_Cure); if(tar->HasGroup()) { @@ -2690,43 +2737,67 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { // ok, this function should load the list, and the parent list then shove them into the struct and sort npc_spells_id = iDBSpellsID; AIBot_spells.clear(); - if (iDBSpellsID == 0) { + if (!iDBSpellsID) { AIautocastspell_timer->Disable(); return false; } - DBbotspells_Struct* spell_list = content_db.GetBotSpells(iDBSpellsID); + + auto* spell_list = content_db.GetBotSpells(iDBSpellsID); if (!spell_list) { AIautocastspell_timer->Disable(); return false; } - DBbotspells_Struct* parentlist = content_db.GetBotSpells(spell_list->parent_list); - std::string debug_msg = StringFormat("Loading NPCSpells onto %s: dbspellsid=%u, level=%u", GetName(), iDBSpellsID, GetLevel()); + auto* parentlist = content_db.GetBotSpells(spell_list->parent_list); + + auto debug_msg = fmt::format( + "Loading NPCSpells onto {}: dbspellsid={}, level={}", + GetName(), + iDBSpellsID, + GetLevel() + ); + if (spell_list) { - debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->entries.size(), spell_list->parent_list)); + debug_msg.append( + fmt::format( + " (found, {}), parentlist={}", + spell_list->entries.size(), + spell_list->parent_list + ) + ); + if (spell_list->parent_list) { - if (parentlist) - debug_msg.append(StringFormat(" (found, %u)", parentlist->entries.size())); - else + if (parentlist) { + debug_msg.append( + fmt::format( + " (found, {})", + parentlist->entries.size() + ) + ); + } else { debug_msg.append(" (not found)"); + } } - } - else { + } else { debug_msg.append(" (not found)"); } - LogAIDetail("[{}]", debug_msg.c_str()); + + LogAIDetail("[{}]", debug_msg); if (parentlist) { for (const auto &iter : parentlist->entries) { LogAI("([{}]) [{}]", iter.spellid, spells[iter.spellid].name); } } + LogAIModerate("fin (parent list)"); + if (spell_list) { for (const auto &iter : spell_list->entries) { LogAIDetail("([{}]) [{}]", iter.spellid, spells[iter.spellid].name); } } + LogAIModerate("fin (spell list)"); uint16 attack_proc_spell = -1; @@ -2768,14 +2839,21 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { _idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max; _idle_beneficial_chance = parentlist->idle_beneficial_chance; for (auto &e : parentlist->entries) { - if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) { - if (!IsSpellInBotList(spell_list, e.spellid)) - { - AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp); + if ( + EQ::ValueWithin(GetLevel(), e.minlevel, e.maxlevel) && + e.spellid && + !IsSpellInBotList(spell_list, e.spellid) + ) { + if (!e.bucket_name.empty() && !e.bucket_value.empty()) { + if (!CheckDataBucket(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); } } } + if (spell_list->attack_proc >= 0) { attack_proc_spell = spell_list->attack_proc; proc_chance = spell_list->proc_chance; @@ -2792,10 +2870,20 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { } //If any casting variables are defined in the current list, ignore those in the parent list. - if (spell_list->fail_recast || spell_list->engaged_no_sp_recast_min || spell_list->engaged_no_sp_recast_max - || spell_list->engaged_beneficial_self_chance || spell_list->engaged_beneficial_other_chance || spell_list->engaged_detrimental_chance - || spell_list->pursue_no_sp_recast_min || spell_list->pursue_no_sp_recast_max || spell_list->pursue_detrimental_chance - || spell_list->idle_no_sp_recast_min || spell_list->idle_no_sp_recast_max || spell_list->idle_beneficial_chance) { + if ( + spell_list->fail_recast || + spell_list->engaged_no_sp_recast_min || + spell_list->engaged_no_sp_recast_max || + spell_list->engaged_beneficial_self_chance || + spell_list->engaged_beneficial_other_chance || + spell_list->engaged_detrimental_chance || + spell_list->pursue_no_sp_recast_min || + spell_list->pursue_no_sp_recast_max || + spell_list->pursue_detrimental_chance || + spell_list->idle_no_sp_recast_min || + spell_list->idle_no_sp_recast_max || + spell_list->idle_beneficial_chance + ) { _fail_recast = spell_list->fail_recast; _engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min; _engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max; @@ -2811,8 +2899,13 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { } for (auto &e : spell_list->entries) { - if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) { - AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp); + if (EQ::ValueWithin(GetLevel(), e.minlevel, e.maxlevel) && e.spellid) { + if (!e.bucket_name.empty() && !e.bucket_value.empty()) { + if (!CheckDataBucket(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); } } @@ -2823,7 +2916,7 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { if (IsValidSpell(attack_proc_spell)) { AddProcToWeapon(attack_proc_spell, true, proc_chance); - if (RuleB(Spells, NPCInnateProcOverride)) { + if (RuleB(Spells, NPCInnateProcOverride)) { innate_proc_spell_id = attack_proc_spell; } } @@ -2860,14 +2953,20 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) { } bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) { - auto it = std::find_if(spell_list->entries.begin(), spell_list->entries.end(), - [iSpellID](const DBbotspells_entries_Struct &a) { return a.spellid == iSpellID; }); + auto it = std::find_if( + spell_list->entries.begin(), + spell_list->entries.end(), + [iSpellID](const DBbotspells_entries_Struct &a) { + return a.spellid == iSpellID; + } + ); + return it != spell_list->entries.end(); } DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID) { - if (iDBSpellsID == 0) { + if (!iDBSpellsID) { return nullptr; } @@ -2880,99 +2979,112 @@ DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID) if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already Bot_Spells_LoadTried.insert(iDBSpellsID); - std::string query = StringFormat("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=%d", - iDBSpellsID); - auto results = QueryDatabase(query); - if (!results.Success()) { - return nullptr; - } + 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 + ); - if (results.RowCount() != 1) { + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { return nullptr; } auto row = results.begin(); DBbotspells_Struct spell_set; - spell_set.parent_list = atoi(row[1]); - spell_set.attack_proc = atoi(row[2]); - spell_set.proc_chance = atoi(row[3]); - spell_set.range_proc = atoi(row[4]); - spell_set.rproc_chance = atoi(row[5]); - spell_set.defensive_proc = atoi(row[6]); - spell_set.dproc_chance = atoi(row[7]); - spell_set.fail_recast = atoi(row[8]); - spell_set.engaged_no_sp_recast_min = atoi(row[9]); - spell_set.engaged_no_sp_recast_max = atoi(row[10]); - spell_set.engaged_beneficial_self_chance = atoi(row[11]); - spell_set.engaged_beneficial_other_chance = atoi(row[12]); - spell_set.engaged_detrimental_chance = atoi(row[13]); - spell_set.pursue_no_sp_recast_min = atoi(row[14]); - spell_set.pursue_no_sp_recast_max = atoi(row[15]); - spell_set.pursue_detrimental_chance = atoi(row[16]); - spell_set.idle_no_sp_recast_min = atoi(row[17]); - spell_set.idle_no_sp_recast_max = atoi(row[18]); - spell_set.idle_beneficial_chance = atoi(row[19]); + 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])); // pulling fixed values from an auto-increment field is dangerous... - query = StringFormat( - "SELECT spellid, type, minlevel, maxlevel, " - "manacost, recast_delay, priority, min_hp, max_hp, resist_adjust " - "FROM bot_spells_entries " - "WHERE npc_spells_id=%d ORDER BY minlevel", - iDBSpellsID); + 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 + ); results = QueryDatabase(query); - - if (!results.Success()) { + if (!results.Success() || !results.RowCount()) { return nullptr; } - int entryIndex = 0; - for (row = results.begin(); row != results.end(); ++row, ++entryIndex) { + for (auto row : results) { DBbotspells_entries_Struct entry; - int spell_id = atoi(row[0]); + auto spell_id = std::stoi(row[0]); entry.spellid = spell_id; - entry.type = atoul(row[1]); - entry.minlevel = atoi(row[2]); - entry.maxlevel = atoi(row[3]); - entry.manacost = atoi(row[4]); - entry.recast_delay = atoi(row[5]); - entry.priority = atoi(row[6]); - entry.min_hp = atoi(row[7]); - entry.max_hp = atoi(row[8]); + 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])); // some spell types don't make much since to be priority 0, so fix that - if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) + if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) { entry.priority = 1; + } if (row[9]) { - entry.resist_adjust = atoi(row[9]); - } - else if (IsValidSpell(spell_id)) { + entry.resist_adjust = static_cast(std::stoi(row[9])); + } else if (IsValidSpell(spell_id)) { entry.resist_adjust = spells[spell_id].resist_difficulty; } + spell_set.entries.push_back(entry); } Bot_Spells_Cache.insert(std::make_pair(iDBSpellsID, spell_set)); return &Bot_Spells_Cache[iDBSpellsID]; - } + } return nullptr; } // adds a spell to the list, taking into account priority and resorting list as needed. -void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, - int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp) -{ - +void Bot::AddSpellToBotList( + int16 iPriority, + uint16 iSpellID, + uint32 iType, + int16 iManaCost, + int32 iRecastDelay, + int16 iResistAdjust, + int8 min_hp, + int8 max_hp, + std::string bucket_name, + std::string bucket_value, + uint8 bucket_comparison +) { if(!IsValidSpell(iSpellID)) { return; } @@ -2989,6 +3101,9 @@ void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, t.resist_adjust = iResistAdjust; t.min_hp = min_hp; t.max_hp = max_hp; + t.bucket_name = bucket_name; + t.bucket_value = bucket_value; + t.bucket_comparison = bucket_comparison; AIBot_spells.push_back(t); diff --git a/zone/client.cpp b/zone/client.cpp index ca887049f..daccf2779 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -11607,149 +11607,6 @@ void Client::SendReloadCommandMessages() { SendChatLineBreak(); } -bool Client::CheckMerchantDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value) -{ - std::vector bucket_checks; - bool found = false; - bool passes = false; - - switch (bucket_comparison) { - case MerchantBucketComparison::BucketEqualTo: - { - if (player_value != bucket_value) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketNotEqualTo: - { - if (player_value == bucket_value) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketGreaterThanOrEqualTo: - { - if (player_value < bucket_value) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketLesserThanOrEqualTo: - { - if (player_value > bucket_value) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketGreaterThan: - { - if (player_value <= bucket_value) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketLesserThan: - { - if (player_value >= bucket_value) { - break; - } - - passes = true; - - break; - } - case MerchantBucketComparison::BucketIsAny: - { - bucket_checks = Strings::Split(bucket_value, "|"); - if (bucket_checks.empty()) { - break; - } - - for (const auto &bucket : bucket_checks) { - if (player_value == bucket) { - found = true; - break; - } - } - - if (!found) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketIsNotAny: - { - bucket_checks = Strings::Split(bucket_value, "|"); - if (bucket_checks.empty()) { - break; - } - - for (const auto &bucket : bucket_checks) { - if (player_value == bucket) { - found = true; - break; - } - } - - if (found) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketIsBetween: - { - bucket_checks = Strings::Split(bucket_value, "|"); - if (bucket_checks.empty()) { - break; - } - - if ( - std::stoll(player_value) < std::stoll(bucket_checks[0]) || - std::stoll(player_value) > std::stoll(bucket_checks[1]) - ) { - break; - } - - passes = true; - break; - } - case MerchantBucketComparison::BucketIsNotBetween: - { - bucket_checks = Strings::Split(bucket_value, "|"); - if (bucket_checks.empty()) { - break; - } - - if ( - std::stoll(player_value) >= std::stoll(bucket_checks[0]) && - std::stoll(player_value) <= std::stoll(bucket_checks[1]) - ) { - break; - } - - passes = true; - break; - } - } - - return passes; -} - std::map Client::GetMerchantDataBuckets() { std::map merchant_data_buckets; diff --git a/zone/client.h b/zone/client.h index fabafe2a8..6fea80883 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1633,7 +1633,6 @@ public: Timer m_list_task_timers_rate_limit = {}; std::map GetMerchantDataBuckets(); - bool CheckMerchantDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value); protected: friend class Mob; diff --git a/zone/client_process.cpp b/zone/client_process.cpp index eb8c53249..9c9e05800 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -855,7 +855,7 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) { continue; } - if (!CheckMerchantDataBucket(ml.bucket_comparison, bucket_value, player_value)) { + if (!zone->CheckDataBucket(ml.bucket_comparison, bucket_value, player_value)) { continue; } } diff --git a/zone/merc.cpp b/zone/merc.cpp index c21984773..990d71187 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -2904,7 +2904,7 @@ std::list Merc::GetMercSpellsBySpellType(Merc* caster, uint32 spellTy std::vector mercSpellList = caster->GetMercSpells(); for (int i = mercSpellList.size() - 1; i >= 0; i--) { - if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(mercSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -2941,7 +2941,7 @@ MercSpell Merc::GetFirstMercSpellBySpellType(Merc* caster, uint32 spellType) { std::vector mercSpellList = caster->GetMercSpells(); for (int i = mercSpellList.size() - 1; i >= 0; i--) { - if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(mercSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -2979,7 +2979,7 @@ MercSpell Merc::GetMercSpellBySpellID(Merc* caster, uint16 spellid) { std::vector mercSpellList = caster->GetMercSpells(); for (int i = mercSpellList.size() - 1; i >= 0; i--) { - if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(mercSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -3009,7 +3009,7 @@ std::list Merc::GetMercSpellsForSpellEffect(Merc* caster, int spellEf std::vector mercSpellList = caster->GetMercSpells(); for (int i = mercSpellList.size() - 1; i >= 0; i--) { - if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(mercSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; @@ -3039,7 +3039,7 @@ std::list Merc::GetMercSpellsForSpellEffectAndTargetType(Merc* caster std::vector mercSpellList = caster->GetMercSpells(); for (int i = mercSpellList.size() - 1; i >= 0; i--) { - if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(mercSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here continue; diff --git a/zone/mob.cpp b/zone/mob.cpp index 5454bee0f..8614d30e2 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -6867,6 +6867,10 @@ std::string Mob::GetBucketKey() { return fmt::format("character-{}", CastToClient()->CharacterID()); } else if (IsNPC()) { return fmt::format("npc-{}", GetNPCTypeID()); +#ifdef BOTS + } else if (IsBot()) { + return fmt::format("bot-{}", CastToBot()->GetBotID()); +#endif } return std::string(); } diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index e28167c97..8b35ba010 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -84,7 +84,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates float manaR = GetManaRatio(); for (int i = static_cast(AIspells.size()) - 1; i >= 0; i--) { - if (AIspells[i].spellid <= 0 || AIspells[i].spellid >= SPDAT_RECORDS) { + if (!IsValidSpell(AIspells[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here //return false; @@ -2133,7 +2133,7 @@ bool Mob::Flurry(ExtraAttackOptions *opts) GetCleanName(), target->GetCleanName()); } - + int num_attacks = GetSpecialAbilityParam(SPECATK_FLURRY, 1); num_attacks = num_attacks > 0 ? num_attacks : RuleI(Combat, MaxFlurryHits); for (int i = 0; i < num_attacks; i++) @@ -2171,7 +2171,7 @@ bool Mob::Rampage(ExtraAttackOptions *opts) entity_list.MessageCloseString(this, true, 200, Chat::PetFlurry, NPC_RAMPAGE, GetCleanName()); } else { entity_list.MessageCloseString(this, true, 200, Chat::NPCRampage, NPC_RAMPAGE, GetCleanName()); - } + } int rampage_targets = GetSpecialAbilityParam(SPECATK_RAMPAGE, 1); if (rampage_targets == 0) // if set to 0 or not set in the DB rampage_targets = RuleI(Combat, DefaultRampageTargets); diff --git a/zone/npc.h b/zone/npc.h index fa70c3b88..8020b5cb1 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -37,14 +37,14 @@ #endif typedef struct { - float min_x; - float max_x; - float min_y; - float max_y; - float min_z; - float max_z; - bool say; - bool proximity_set; + float min_x; + float max_x; + float min_y; + float max_y; + float min_z; + float max_z; + bool say; + bool proximity_set; } NPCProximity; struct AISpells_Struct { @@ -60,15 +60,18 @@ struct AISpells_Struct { }; struct BotSpells_Struct { - uint32 type; // 0 = never, must be one (and only one) of the defined values - int16 spellid; // <= 0 = no spell - int16 manacost; // -1 = use spdat, -2 = no cast time - uint32 time_cancast; // when we can cast this spell next - int32 recast_delay; - int16 priority; - int16 resist_adjust; - int16 min_hp; // >0 won't cast if HP is below - int16 max_hp; // >0 won't cast if HP is above + uint32 type; // 0 = never, must be one (and only one) of the defined values + int16 spellid; // <= 0 = no spell + int16 manacost; // -1 = use spdat, -2 = no cast time + uint32 time_cancast; // when we can cast this spell next + int32 recast_delay; + int16 priority; + int16 resist_adjust; + int16 min_hp; // >0 won't cast if HP is below + int16 max_hp; // >0 won't cast if HP is above + std::string bucket_name; + std::string bucket_value; + uint8 bucket_comparison; }; struct AISpellsEffects_Struct { @@ -79,17 +82,17 @@ struct AISpellsEffects_Struct { }; struct AISpellsVar_Struct { - uint32 fail_recast; + uint32 fail_recast; uint32 engaged_no_sp_recast_min; uint32 engaged_no_sp_recast_max; uint8 engaged_beneficial_self_chance; uint8 engaged_beneficial_other_chance; uint8 engaged_detrimental_chance; - uint32 pursue_no_sp_recast_min; - uint32 pursue_no_sp_recast_max; - uint8 pursue_detrimental_chance; - uint32 idle_no_sp_recast_min; - uint32 idle_no_sp_recast_max; + uint32 pursue_no_sp_recast_min; + uint32 pursue_no_sp_recast_max; + uint8 pursue_detrimental_chance; + uint32 idle_no_sp_recast_min; + uint32 idle_no_sp_recast_max; uint8 idle_beneficial_chance; }; diff --git a/zone/zone.cpp b/zone/zone.cpp old mode 100755 new mode 100644 index 8a85bd61c..dcf4bf123 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1875,7 +1875,7 @@ bool Zone::Depop(bool StartSpawnTimer) { // clear spell cache database.ClearNPCSpells(); database.ClearBotSpells(); - + zone->spawn_group_list.ReloadSpawnGroups(); return true; @@ -2976,3 +2976,178 @@ std::string Zone::GetAAName(int aa_id) return std::string(); } + +bool Zone::CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value) +{ + std::vector bucket_checks; + bool found = false; + bool passes = false; + + switch (bucket_comparison) { + case BucketComparison::BucketEqualTo: + { + if (player_value != bucket_value) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketNotEqualTo: + { + if (player_value == bucket_value) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketGreaterThanOrEqualTo: + { + if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) { + break; + } + + if (std::stoll(player_value) < std::stoll(bucket_value)) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketLesserThanOrEqualTo: + { + if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) { + break; + } + + if (std::stoll(player_value) > std::stoll(bucket_value)) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketGreaterThan: + { + if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) { + break; + } + + if (std::stoll(player_value) <= std::stoll(bucket_value)) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketLesserThan: + { + if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) { + break; + } + + if (std::stoll(player_value) >= std::stoll(bucket_value)) { + break; + } + + passes = true; + + break; + } + case BucketComparison::BucketIsAny: + { + bucket_checks = Strings::Split(bucket_value, "|"); + if (bucket_checks.empty()) { + break; + } + + if (std::find(bucket_checks.begin(), bucket_checks.end(), player_value) != bucket_checks.end()) { + found = true; + } + + if (!found) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketIsNotAny: + { + bucket_checks = Strings::Split(bucket_value, "|"); + if (bucket_checks.empty()) { + break; + } + + if (std::find(bucket_checks.begin(), bucket_checks.end(), player_value) != bucket_checks.end()) { + found = true; + } + + if (found) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketIsBetween: + { + bucket_checks = Strings::Split(bucket_value, "|"); + if (bucket_checks.empty()) { + break; + } + + if ( + !Strings::IsNumber(player_value) || + !Strings::IsNumber(bucket_checks[0]) || + !Strings::IsNumber(bucket_checks[1]) + ) { + break; + } + + if ( + !EQ::ValueWithin( + std::stoll(player_value), + std::stoll(bucket_checks[0]), + std::stoll(bucket_checks[1]) + ) + ) { + break; + } + + passes = true; + break; + } + case BucketComparison::BucketIsNotBetween: + { + bucket_checks = Strings::Split(bucket_value, "|"); + if (bucket_checks.empty()) { + break; + } + + if ( + !Strings::IsNumber(player_value) || + !Strings::IsNumber(bucket_checks[0]) || + !Strings::IsNumber(bucket_checks[1]) + ) { + break; + } + + if ( + EQ::ValueWithin( + std::stoll(player_value), + std::stoll(bucket_checks[0]), + std::stoll(bucket_checks[1]) + ) + ) { + break; + } + + passes = true; + break; + } + } + + return passes; +} diff --git a/zone/zone.h b/zone/zone.h index 3ffd83439..28b302cf0 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -311,6 +311,8 @@ public: bool IsQuestHotReloadQueued() const; void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued); + bool CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value); + WaterMap *watermap; ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f); diff --git a/zone/zonedb.h b/zone/zonedb.h index 3daf0a123..103dd3984 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -109,16 +109,19 @@ struct DBnpcspellseffects_Struct { #pragma pack(1) struct DBbotspells_entries_Struct { - uint16 spellid; - uint8 minlevel; - uint8 maxlevel; - uint32 type; - int16 manacost; - int16 priority; - int32 recast_delay; - int16 resist_adjust; - int8 min_hp; - int8 max_hp; + uint16 spellid; + uint8 minlevel; + uint8 maxlevel; + uint32 type; + int16 manacost; + int16 priority; + int32 recast_delay; + int16 resist_adjust; + int8 min_hp; + int8 max_hp; + std::string bucket_name; + std::string bucket_value; + uint8 bucket_comparison; }; #pragma pack() @@ -136,11 +139,11 @@ struct DBbotspells_Struct { uint8 engaged_beneficial_self_chance; uint8 engaged_beneficial_other_chance; uint8 engaged_detrimental_chance; - uint32 pursue_no_sp_recast_min; - uint32 pursue_no_sp_recast_max; - uint8 pursue_detrimental_chance; - uint32 idle_no_sp_recast_min; - uint32 idle_no_sp_recast_max; + uint32 pursue_no_sp_recast_min; + uint32 pursue_no_sp_recast_max; + uint8 pursue_detrimental_chance; + uint32 idle_no_sp_recast_min; + uint32 idle_no_sp_recast_max; uint8 idle_beneficial_chance; std::vector entries; };