diff --git a/common/repositories/base/base_bot_data_repository.h b/common/repositories/base/base_bot_data_repository.h index da0b67af7..b4d2c47b2 100644 --- a/common/repositories/base/base_bot_data_repository.h +++ b/common/repositories/base/base_bot_data_repository.h @@ -69,6 +69,7 @@ public: int32_t expansion_bitmask; uint8_t enforce_spell_settings; uint8_t archery_setting; + uint32_t caster_range; }; static std::string PrimaryKey() @@ -129,6 +130,7 @@ public: "expansion_bitmask", "enforce_spell_settings", "archery_setting", + "caster_range", }; } @@ -185,6 +187,7 @@ public: "expansion_bitmask", "enforce_spell_settings", "archery_setting", + "caster_range", }; } @@ -275,6 +278,7 @@ public: e.expansion_bitmask = -1; e.enforce_spell_settings = 0; e.archery_setting = 0; + e.caster_range = 0; return e; } @@ -361,6 +365,7 @@ public: e.expansion_bitmask = static_cast(atoi(row[47])); e.enforce_spell_settings = static_cast(strtoul(row[48], nullptr, 10)); e.archery_setting = static_cast(strtoul(row[49], nullptr, 10)); + e.caster_range = static_cast(strtoul(row[50], nullptr, 10)); return e; } @@ -443,6 +448,7 @@ public: v.push_back(columns[47] + " = " + std::to_string(e.expansion_bitmask)); v.push_back(columns[48] + " = " + std::to_string(e.enforce_spell_settings)); v.push_back(columns[49] + " = " + std::to_string(e.archery_setting)); + v.push_back(columns[50] + " = " + std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -514,6 +520,7 @@ public: v.push_back(std::to_string(e.expansion_bitmask)); v.push_back(std::to_string(e.enforce_spell_settings)); v.push_back(std::to_string(e.archery_setting)); + v.push_back(std::to_string(e.caster_range)); auto results = db.QueryDatabase( fmt::format( @@ -593,6 +600,7 @@ public: v.push_back(std::to_string(e.expansion_bitmask)); v.push_back(std::to_string(e.enforce_spell_settings)); v.push_back(std::to_string(e.archery_setting)); + v.push_back(std::to_string(e.caster_range)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -676,6 +684,7 @@ public: e.expansion_bitmask = static_cast(atoi(row[47])); e.enforce_spell_settings = static_cast(strtoul(row[48], nullptr, 10)); e.archery_setting = static_cast(strtoul(row[49], nullptr, 10)); + e.caster_range = static_cast(strtoul(row[50], nullptr, 10)); all_entries.push_back(e); } @@ -750,6 +759,7 @@ public: e.expansion_bitmask = static_cast(atoi(row[47])); e.enforce_spell_settings = static_cast(strtoul(row[48], nullptr, 10)); e.archery_setting = static_cast(strtoul(row[49], nullptr, 10)); + e.caster_range = static_cast(strtoul(row[50], nullptr, 10)); all_entries.push_back(e); } diff --git a/common/version.h b/common/version.h index f24773b02..795931a1c 100644 --- a/common/version.h +++ b/common/version.h @@ -43,7 +43,7 @@ */ #define CURRENT_BINARY_DATABASE_VERSION 9220 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9037 +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9038 #endif diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index f0c0a78ee..32191b95a 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -36,6 +36,7 @@ 9035|2022_12_04_bot_archery.sql|SHOW COLUMNS FROM `bot_data` LIKE 'archery_setting'|empty| 9036|2023_01_19_drop_bot_views.sql|SHOW TABLES LIKE 'vw_groups'|not_empty| 9037|2023_01_22_add_name_index.sql||show index from bot_data WHERE key_name = 'name`|empty| +9038|2023_02_16_add_caster_range.sql|SHOW COLUMNS FROM `bot_data` LIKE 'caster_range'|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/2023_02_16_add_caster_range.sql b/utils/sql/git/bots/required/2023_02_16_add_caster_range.sql new file mode 100644 index 000000000..0a2946164 --- /dev/null +++ b/utils/sql/git/bots/required/2023_02_16_add_caster_range.sql @@ -0,0 +1,2 @@ +ALTER TABLE `bot_data` +ADD COLUMN `caster_range` INT(11) UNSIGNED NOT NULL DEFAULT '300' AFTER `archery_setting`; \ No newline at end of file diff --git a/zone/bot.cpp b/zone/bot.cpp index 6ecb437c8..58ffd377d 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -71,6 +71,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm m_enforce_spell_settings = 0; m_bot_archery_setting = 0; m_expansion_bitmask = -1; + m_bot_caster_range = 0; SetBotID(0); SetBotSpellID(0); SetSpawnStatus(false); @@ -3115,26 +3116,7 @@ void Bot::AI_Process() } } float melee_distance_min = melee_distance / 2.0f; - - // Calculate caster distances - float caster_distance_max = 0.0f; - float caster_distance_min = 0.0f; - float caster_distance = 0.0f; - { - if (GetLevel() >= GetStopMeleeLevel() && GetClass() >= WARRIOR && GetClass() <= BERSERKER) { - caster_distance_max = MAX_CASTER_DISTANCE[(GetClass() - 1)]; - } - - if (caster_distance_max) { - - caster_distance_min = melee_distance_max; - if (caster_distance_max <= caster_distance_min) { - caster_distance_max = caster_distance_min * 1.25f; - } - - caster_distance = ((caster_distance_max + caster_distance_min) / 2); - } - } + float caster_distance_max = GetBotCasterMaxRange(melee_distance_max); bool atArcheryRange = IsArcheryRange(tar); @@ -3157,11 +3139,11 @@ void Bot::AI_Process() ChangeBotArcherWeapons(IsBotArcher()); } } - + bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); if (IsBotArcher() && atArcheryRange) { atCombatRange = true; } - else if (caster_distance_max && tar_distance <= caster_distance_max) { + else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) { atCombatRange = true; } else if (tar_distance <= melee_distance) { @@ -9825,4 +9807,22 @@ void Bot::SendSpellAnim(uint16 target_id, uint16 spell_id) entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles)); } +float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster distances + float caster_distance_max = 0.0f; + float caster_distance_min = 0.0f; + float caster_distance = 0.0f; + + caster_distance_max = GetBotCasterRange() * GetBotCasterRange(); + if (!GetBotCasterRange() && GetLevel() >= GetStopMeleeLevel() && GetClass() >= WARRIOR && GetClass() <= BERSERKER) { + caster_distance_max = MAX_CASTER_DISTANCE[GetClass() - 1]; + } + if (caster_distance_max) { + caster_distance_min = melee_distance_max; + if (caster_distance_max <= caster_distance_min) { + caster_distance_max = caster_distance_min * 1.25f; + } + } + return caster_distance_max; +} + uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; diff --git a/zone/bot.h b/zone/bot.h index 5219190df..fdddfe172 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -348,6 +348,8 @@ public: void SetStopMeleeLevel(uint8 level); void SetGuardMode(); void SetHoldMode(); + uint32 GetBotCasterRange() { return m_bot_caster_range; } + bool IsValidSpellRange(uint16 spell_id, Mob const* tar); // Bot AI Methods void AI_Bot_Init(); @@ -490,7 +492,7 @@ public: EQ::constants::StanceType GetBotStance() { return _botStance; } uint8 GetChanceToCastBySpellType(uint32 spellType); bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; } - + float GetBotCasterMaxRange(float melee_distance_max); bool IsGroupHealer() { return m_CastingRoles.GroupHealer; } bool IsGroupSlower() { return m_CastingRoles.GroupSlower; } bool IsGroupNuker() { return m_CastingRoles.GroupNuker; } @@ -623,6 +625,7 @@ public: else _botStance = EQ::constants::stancePassive; } + void SetBotCasterRange(uint32 bot_caster_range) { m_bot_caster_range = bot_caster_range; } void SetSpellRecastTimer(int timer_index, int32 recast_delay); void SetDisciplineRecastTimer(int timer_index, int32 recast_delay); void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} @@ -820,7 +823,7 @@ private: bool m_pulling_flag; bool m_returning_flag; eStandingPetOrder m_previous_pet_order; - + uint32 m_bot_caster_range; BotCastingRoles m_CastingRoles; std::map bot_data_buckets; std::map bot_owner_data_buckets; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index a125c3935..d37cac231 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1366,6 +1366,7 @@ int bot_command_init(void) bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_subcommand_bot_title) || bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_subcommand_bot_update) || bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_subcommand_bot_woad) || + bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) || bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_subcommand_circle) || bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || @@ -10848,3 +10849,53 @@ void bot_command_enforce_spell_list(Client* c, const Seperator *sep) ).c_str() ); } + +void bot_command_caster_range(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [current | value: 0 - 300].", sep->arg[0]); + c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); + c->Message(Chat::White, "note: Use [current] to check the current setting."); + c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); + c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command."); + return; + } + if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { + c->Message(Chat::White, "You must a caster or hybrid class to use this command."); + return; + } + + uint32 crange = 0; + if (sep->IsNumber(1)) { + crange = atoi(sep->arg[1]); + if (crange >= 0 && crange <= 300) { + my_bot->SetBotCasterRange(crange); + if (!database.botdb.SaveBotCasterRange(c->CharacterID(), my_bot->GetBotID(), crange)) { + c->Message(Chat::White, "%s for '%s'", BotDatabase::fail::SaveBotCasterRange(), my_bot->GetCleanName()); + return; + } + else { + c->Message(Chat::White, "Successfully set Caster Range for %s to %u.", my_bot->GetCleanName(), crange); + } + } + else { + c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); + return; + } + } + else if (!strcasecmp(sep->arg[1], "current")) { + c->Message(Chat::White, "My current range is %u.", my_bot->GetBotCasterRange()); + } + else { + c->Message(Chat::White, "Incorrect argument, use help for a list of options."); + } +} \ No newline at end of file diff --git a/zone/bot_command.h b/zone/bot_command.h index 9e8715e75..480371537 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -553,6 +553,7 @@ void bot_command_attack(Client *c, const Seperator *sep); void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(Client *c, const Seperator *sep); void bot_command_botgroup(Client *c, const Seperator *sep); +void bot_command_caster_range(Client* c, const Seperator* sep); void bot_command_charm(Client *c, const Seperator *sep); void bot_command_cure(Client *c, const Seperator *sep); void bot_command_defensive(Client *c, const Seperator *sep); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index b241238f0..fccfb129a 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -485,6 +485,8 @@ bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot) loaded_bot->SetBotEnforceSpellSetting((l.enforce_spell_settings ? true : false)); loaded_bot->SetBotArcherySetting((l.archery_setting ? true : false)); + + loaded_bot->SetBotCasterRange(l.caster_range); } return true; @@ -545,6 +547,7 @@ bool BotDatabase::SaveNewBot(Bot* bot_inst, uint32& bot_id) e.expansion_bitmask = bot_inst->GetExpansionBitmask(); e.enforce_spell_settings = bot_inst->GetBotEnforceSpellSetting(); e.archery_setting = bot_inst->IsBotArcher() ? 1 : 0; + e.caster_range = bot_inst->GetBotCasterRange(); auto b = BotDataRepository::InsertOne(database, e); if (!b.bot_id) { @@ -3151,6 +3154,30 @@ std::string BotDatabase::GetBotNameByID(const uint32 bot_id) return nullptr; } +bool BotDatabase::SaveBotCasterRange(const uint32 owner_id, const uint32 bot_id, const uint32 bot_caster_range_value) +{ + if (!owner_id || !bot_id) { + return false; + } + + query = fmt::format( + "UPDATE `bot_data`" + " SET `caster_range` = '{}'" + " WHERE `owner_id` = '{}'" + " AND `bot_id` = '{}'", + bot_caster_range_value, + owner_id, + bot_id + ); + auto results = database.QueryDatabase(query); + + if (!results.Success()) { + return false; + } + + return true; +} + /* fail::Bot functions */ const char* BotDatabase::fail::LoadBotsList() { return "Failed to bots list"; } const char* BotDatabase::fail::LoadOwnerID() { return "Failed to load owner ID"; } @@ -3205,6 +3232,7 @@ const char* BotDatabase::fail::ToggleAllHelmAppearances() { return "Failed to sa const char* BotDatabase::fail::SaveFollowDistance() { return "Failed to save follow distance"; } const char* BotDatabase::fail::SaveAllFollowDistances() { return "Failed to save all follow distances"; } const char* BotDatabase::fail::SaveStopMeleeLevel() { return "Failed to save stop melee level"; } +const char* BotDatabase::fail::SaveBotCasterRange() { return "Failed to save caster range"; } /* fail::Bot heal rotation functions */ const char* BotDatabase::fail::LoadHealRotationIDByBotID() { return "Failed to load heal rotation ID by bot ID"; } diff --git a/zone/bot_database.h b/zone/bot_database.h index e86a31778..8107c0ed2 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -146,6 +146,8 @@ public: bool SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag); bool SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag); + bool SaveBotCasterRange(const uint32 owner_id, const uint32 bot_id, const uint32 bot_caster_range_value); + /* Bot bot-group functions */ bool QueryBotGroupExistence(const std::string& botgroup_name); @@ -250,6 +252,7 @@ public: static const char* SaveFollowDistance(); static const char* SaveAllFollowDistances(); static const char* SaveStopMeleeLevel(); + static const char* SaveBotCasterRange(); /* fail::Bot bot-group functions */ static const char* QueryBotGroupExistence(); diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 570ba072f..75dd51a8d 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -98,8 +98,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { break; } - castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, addMob)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); + } if (castedSpell) { BotGroupSay( this, @@ -260,7 +262,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + } if (castedSpell) { /*if (TempDontHealMeBeforeTime != tar->DontHealMeBefore()) @@ -340,7 +344,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { } uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); + } if (TempDontRootMeBefore != tar->DontRootMeBefore()) { tar->SetDontRootMeBefore(TempDontRootMeBefore); @@ -488,7 +494,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (IsInvulnerabilitySpell(botSpell.SpellId)) { tar = this; //target self for invul type spells } - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + + if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } } break; } @@ -573,7 +582,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { } } - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } } break; } @@ -594,7 +605,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { // TODO: Check target to see if there is anything to dispel if (tar->CountDispellableBuffs() > 0) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } } } break; @@ -768,7 +781,6 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (CheckSpellRecastTimers(this, itr->SpellIndex)) { uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) tar->SetDontBuffMeBefore(TempDontBuffMeBefore); } @@ -797,7 +809,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) break; - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } } break; } @@ -820,7 +834,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); + if (IsValidSpellRange(botSpell.SpellId, tar)) { castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); + } if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) tar->SetDontSnareMeBefore(TempDontSnareMeBefore); @@ -856,7 +872,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); if (TempDontDotMeBefore != tar->DontDotMeBefore()) tar->SetDontDotMeBefore(TempDontDotMeBefore); @@ -888,7 +904,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); + } if (TempDontDotMeBefore != tar->DontDotMeBefore()) tar->SetDontDotMeBefore(TempDontDotMeBefore); @@ -929,7 +947,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + } if (castedSpell) break; } @@ -956,7 +976,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) break; - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } if (castedSpell && GetClass() != BARD) { BotGroupSay( @@ -992,7 +1014,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) break; - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } } break; } @@ -1006,7 +1030,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); if (castedSpell) { if (botClass != BARD) { @@ -1054,7 +1078,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + } if (castedSpell) { BotGroupSay( this, @@ -1096,7 +1122,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); if (castedSpell) break; } @@ -1128,7 +1154,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) continue; - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); if (castedSpell) break; } @@ -1764,8 +1790,9 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { return false; uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); - - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + if (IsValidSpellRange(botSpell.SpellId, tar)) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + } if (castedSpell) { BotGroupSay( @@ -3456,3 +3483,17 @@ bool Bot::HasBotSpellEntry(uint16 spellid) { return false; } + +bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { + if (!IsValidSpell(spell_id)) { + return false; + } + + if (tar) { + int spellrange = (GetActSpellRange(spell_id, spells[spell_id].range) * GetActSpellRange(spell_id, spells[spell_id].range)); + if (spellrange >= DistanceSquared(m_Position, tar->GetPosition())) { + return true; + } + } + return false; +} \ No newline at end of file