diff --git a/common/database/database_update_manifest_bots.cpp b/common/database/database_update_manifest_bots.cpp index 8f118e47c..563ce69f7 100644 --- a/common/database/database_update_manifest_bots.cpp +++ b/common/database/database_update_manifest_bots.cpp @@ -82,6 +82,28 @@ CREATE TABLE `bot_starting_items` ( `content_flags_disabled` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci; +)", + }, + ManifestEntry{ + .version = 9041, + .description = "2023_12_04_bot_timers.sql", + .check = "SHOW COLUMNS FROM `bot_timers` LIKE 'recast_time'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `bot_timers` + ADD COLUMN `recast_time` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `timer_value`, + ADD COLUMN `is_spell` TINYINT(2) UNSIGNED NOT NULL DEFAULT 0 AFTER `recast_time`, + ADD COLUMN `is_disc` TINYINT(2) UNSIGNED NOT NULL DEFAULT 0 AFTER `is_spell`, + ADD COLUMN `spell_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `is_disc`, + ADD COLUMN `is_item` TINYINT(2) UNSIGNED NOT NULL DEFAULT 0 AFTER `spell_id`, + ADD COLUMN `item_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `is_item`; +ALTER TABLE `bot_timers` + DROP FOREIGN KEY `FK_bot_timers_1`; +ALTER TABLE `bot_timers` + DROP PRIMARY KEY; +ALTER TABLE `bot_timers` + ADD PRIMARY KEY (`bot_id`, `timer_id`, `spell_id`, `item_id`); )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/repositories/base/base_bot_timers_repository.h b/common/repositories/base/base_bot_timers_repository.h index 16858859b..45bb495be 100644 --- a/common/repositories/base/base_bot_timers_repository.h +++ b/common/repositories/base/base_bot_timers_repository.h @@ -16,12 +16,19 @@ #include "../../strings.h" #include + class BaseBotTimersRepository { public: struct BotTimers { uint32_t bot_id; uint32_t timer_id; uint32_t timer_value; + uint32_t recast_time; + uint8_t is_spell; + uint8_t is_disc; + uint32_t spell_id; + uint8_t is_item; + uint32_t item_id; }; static std::string PrimaryKey() @@ -35,6 +42,12 @@ public: "bot_id", "timer_id", "timer_value", + "recast_time", + "is_spell", + "is_disc", + "spell_id", + "is_item", + "item_id", }; } @@ -44,6 +57,12 @@ public: "bot_id", "timer_id", "timer_value", + "recast_time", + "is_spell", + "is_disc", + "spell_id", + "is_item", + "item_id", }; } @@ -87,6 +106,12 @@ public: e.bot_id = 0; e.timer_id = 0; e.timer_value = 0; + e.recast_time = 0; + e.is_spell = 0; + e.is_disc = 0; + e.spell_id = 0; + e.is_item = 0; + e.item_id = 0; return e; } @@ -112,8 +137,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), bot_timers_id ) ); @@ -125,6 +151,12 @@ public: e.bot_id = static_cast(strtoul(row[0], nullptr, 10)); e.timer_id = static_cast(strtoul(row[1], nullptr, 10)); e.timer_value = static_cast(strtoul(row[2], nullptr, 10)); + e.recast_time = static_cast(strtoul(row[3], nullptr, 10)); + e.is_spell = static_cast(strtoul(row[4], nullptr, 10)); + e.is_disc = static_cast(strtoul(row[5], nullptr, 10)); + e.spell_id = static_cast(strtoul(row[6], nullptr, 10)); + e.is_item = static_cast(strtoul(row[7], nullptr, 10)); + e.item_id = static_cast(strtoul(row[8], nullptr, 10)); return e; } @@ -161,6 +193,12 @@ public: v.push_back(columns[0] + " = " + std::to_string(e.bot_id)); v.push_back(columns[1] + " = " + std::to_string(e.timer_id)); v.push_back(columns[2] + " = " + std::to_string(e.timer_value)); + v.push_back(columns[3] + " = " + std::to_string(e.recast_time)); + v.push_back(columns[4] + " = " + std::to_string(e.is_spell)); + v.push_back(columns[5] + " = " + std::to_string(e.is_disc)); + v.push_back(columns[6] + " = " + std::to_string(e.spell_id)); + v.push_back(columns[7] + " = " + std::to_string(e.is_item)); + v.push_back(columns[8] + " = " + std::to_string(e.item_id)); auto results = db.QueryDatabase( fmt::format( @@ -185,6 +223,12 @@ public: v.push_back(std::to_string(e.bot_id)); v.push_back(std::to_string(e.timer_id)); v.push_back(std::to_string(e.timer_value)); + v.push_back(std::to_string(e.recast_time)); + v.push_back(std::to_string(e.is_spell)); + v.push_back(std::to_string(e.is_disc)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.is_item)); + v.push_back(std::to_string(e.item_id)); auto results = db.QueryDatabase( fmt::format( @@ -217,6 +261,12 @@ public: v.push_back(std::to_string(e.bot_id)); v.push_back(std::to_string(e.timer_id)); v.push_back(std::to_string(e.timer_value)); + v.push_back(std::to_string(e.recast_time)); + v.push_back(std::to_string(e.is_spell)); + v.push_back(std::to_string(e.is_disc)); + v.push_back(std::to_string(e.spell_id)); + v.push_back(std::to_string(e.is_item)); + v.push_back(std::to_string(e.item_id)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -253,6 +303,12 @@ public: e.bot_id = static_cast(strtoul(row[0], nullptr, 10)); e.timer_id = static_cast(strtoul(row[1], nullptr, 10)); e.timer_value = static_cast(strtoul(row[2], nullptr, 10)); + e.recast_time = static_cast(strtoul(row[3], nullptr, 10)); + e.is_spell = static_cast(strtoul(row[4], nullptr, 10)); + e.is_disc = static_cast(strtoul(row[5], nullptr, 10)); + e.spell_id = static_cast(strtoul(row[6], nullptr, 10)); + e.is_item = static_cast(strtoul(row[7], nullptr, 10)); + e.item_id = static_cast(strtoul(row[8], nullptr, 10)); all_entries.push_back(e); } @@ -280,6 +336,12 @@ public: e.bot_id = static_cast(strtoul(row[0], nullptr, 10)); e.timer_id = static_cast(strtoul(row[1], nullptr, 10)); e.timer_value = static_cast(strtoul(row[2], nullptr, 10)); + e.recast_time = static_cast(strtoul(row[3], nullptr, 10)); + e.is_spell = static_cast(strtoul(row[4], nullptr, 10)); + e.is_disc = static_cast(strtoul(row[5], nullptr, 10)); + e.spell_id = static_cast(strtoul(row[6], nullptr, 10)); + e.is_item = static_cast(strtoul(row[7], nullptr, 10)); + e.item_id = static_cast(strtoul(row[8], nullptr, 10)); all_entries.push_back(e); } diff --git a/common/ruletypes.h b/common/ruletypes.h index 42d17d81e..740f1838e 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -656,6 +656,9 @@ RULE_BOOL(Bots, AllowPickpocketCommand, true, "Allows the use of the bot command RULE_BOOL(Bots, BotHealOnLevel, false, "Setting whether a bot should heal completely when leveling. Default FALSE.") RULE_INT(Bots, AutosaveIntervalSeconds, 300, "Number of seconds after which a timer is triggered which stores the bot data. The value 0 means no periodic automatic saving.") RULE_BOOL(Bots, CazicTouchBotsOwner, true, "Default True. Cazic Touch/DT will hit bot owner rather than bot.") +RULE_INT(Bots, BotsClickItemsMinLvl, 1, "Minimum level for bots to be able to use ^clickitem. Default 1.") +RULE_BOOL(Bots, BotsCanClickItems, true, "Enabled the ability for bots to click items they have equipped. Default TRUE") +RULE_BOOL(Bots, CanClickMageEpicV1, true, "Whether or not bots are allowed to click Mage Epic 1.0. Default TRUE") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/common/version.h b/common/version.h index fac89df7c..234637b93 100644 --- a/common/version.h +++ b/common/version.h @@ -44,7 +44,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9247 -#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9040 +#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9041 #endif diff --git a/zone/bot.cpp b/zone/bot.cpp index 19e0a0c67..407ab03d1 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -94,6 +94,7 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm SetPullFlag(false); SetPullingFlag(false); SetReturningFlag(false); + SetIsUsingItemClick(false); m_previous_pet_order = SPO_Guard; rest_timer.Disable(); @@ -107,6 +108,8 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm // Do this once and only in this constructor GenerateAppearance(); GenerateBaseStats(); + bot_timers.clear(); + // Calculate HitPoints Last As It Uses Base Stats current_hp = GenerateBaseHitPoints(); current_mana = GenerateBaseManaPoints(); @@ -114,8 +117,6 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm hp_regen = CalcHPRegen(); mana_regen = CalcManaRegen(); end_regen = CalcEnduranceRegen(); - for (int i = 0; i < MaxTimer; i++) - timers[i] = 0; strcpy(name, GetCleanName()); memset(&_botInspectMessage, 0, sizeof(InspectMessage_Struct)); @@ -213,6 +214,7 @@ Bot::Bot( SetPullFlag(false); SetPullingFlag(false); SetReturningFlag(false); + SetIsUsingItemClick(false); m_previous_pet_order = SPO_Guard; rest_timer.Disable(); @@ -238,9 +240,6 @@ Bot::Bot( error_message.clear(); } - for (int i = 0; i < MaxTimer; i++) - timers[i] = 0; - if (GetClass() == Class::Rogue) { m_evade_timer.Start(); } @@ -252,8 +251,10 @@ Bot::Bot( GenerateBaseStats(); - if (!database.botdb.LoadTimers(this) && bot_owner) + bot_timers.clear(); + if (!database.botdb.LoadTimers(this) && bot_owner) { bot_owner->Message(Chat::White, "%s for '%s'", BotDatabase::fail::LoadTimers(), GetCleanName()); + } LoadAAs(); @@ -7916,18 +7917,17 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { return false; if (spell.recast_time > 0) { - if (CheckDisciplineRecastTimers(this, spells[spell_id].timer_id)) { - if (spells[spell_id].timer_id > 0 && spells[spell_id].timer_id < MAX_DISCIPLINE_TIMERS) - SetDisciplineRecastTimer(spells[spell_id].timer_id, spell.recast_time); + if (CheckDisciplineReuseTimer(spell_id)) { + if (spells[spell_id].timer_id > 0) { + SetDisciplineReuseTimer(spell_id); + } } else { - uint32 remaining_time = (GetDisciplineRemainingTime(this, spells[spell_id].timer_id) / 1000); - GetOwner()->Message( - Chat::White, + uint32 remaining_time = (GetDisciplineReuseRemainingTime(spell_id) / 1000); + OwnerMessage( fmt::format( - "{} can use this discipline in {}.", - GetCleanName(), + "I can use this discipline in {}.", Strings::SecondsToTime(remaining_time) - ).c_str() + ) ); return false; } @@ -8810,4 +8810,627 @@ void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id) } } +void Bot::SetSpellRecastTimer(uint16 spell_id, int32 recast_delay) { + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set spell recast timer."); + return; + } + + if (!recast_delay) { + recast_delay = CalcSpellRecastTimer(spell_id); + } + + if (CheckSpellRecastTimer(spell_id)) { + BotTimer_Struct t; + + t.timer_id = spells[ spell_id ].timer_id; + t.timer_value = (Timer::GetCurrentTime() + recast_delay); + t.recast_time = recast_delay; + t.is_spell = true; + t.is_disc = false; + t.spell_id = spells[ spell_id ].id; + t.is_item = false; + t.item_id = 0; + + bot_timers.push_back(t); + } else { + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_spell && + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[ i ].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) { + bot_timers[i].timer_value = (Timer::GetCurrentTime() + recast_delay); + bot_timers[i].recast_time = recast_delay; + break; + } + } + } + } +} + +uint32 Bot::GetSpellRecastTimer(uint16 spell_id) +{ + uint32 result = 0; + + if (spell_id && !IsValidSpell(spell_id)) { + OwnerMessage("Failed to get spell recast timer."); + return result; + } + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_spell && + ( + !spell_id || + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[i].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) + ) { + result = bot_timers[i].timer_value; + break; + } + } + } + + return result; +} + +uint32 Bot::GetSpellRecastRemainingTime(uint16 spell_id) +{ + uint32 result = 0; + + if (GetSpellRecastTimer(spell_id) > Timer::GetCurrentTime()) { + result = (GetSpellRecastTimer(spell_id) - Timer::GetCurrentTime()); + } + + return result; +} + +bool Bot::CheckSpellRecastTimer(uint16 spell_id) +{ + ClearExpiredTimers(); + + if (spell_id && !IsValidSpell(spell_id)) { + OwnerMessage("Failed to check spell recast timer."); + return false; + } + + if (GetSpellRecastTimer(spell_id) < Timer::GetCurrentTime()) { + return true; + } + + return false; +} + +void Bot::SetDisciplineReuseTimer(uint16 spell_id, int32 reuse_timer) +{ + if (!IsValidSpell(spell_id)) { + OwnerMessage("Failed to set discipline reuse timer."); + return; + } + + if (!reuse_timer) { + reuse_timer = CalcSpellRecastTimer(spell_id); + } + + if (CheckDisciplineReuseTimer(spell_id)) { + BotTimer_Struct t; + + t.timer_id = spells[ spell_id ].timer_id; + t.timer_value = (Timer::GetCurrentTime() + reuse_timer); + t.recast_time = reuse_timer; + t.is_spell = false; + t.is_disc = true; + t.spell_id = spells[ spell_id ].id; + t.is_item = false; + t.item_id = 0; + + bot_timers.push_back(t); + } else { + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_disc && + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[i].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) { + bot_timers[i].timer_value = (Timer::GetCurrentTime() + reuse_timer); + bot_timers[i].recast_time = reuse_timer; + break; + } + } + } + } +} + +uint32 Bot::GetDisciplineReuseTimer(uint16 spell_id) +{ + uint32 result = 0; + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_disc && + ( + !spell_id || + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[i].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) + ) { + result = bot_timers[i].timer_value; + break; + } + } + } + + return result; +} + +uint32 Bot::GetDisciplineReuseRemainingTime(uint16 spell_id) { + uint32 result = 0; + + if (GetDisciplineReuseTimer(spell_id) > Timer::GetCurrentTime()) { + result = (GetDisciplineReuseTimer(spell_id) - Timer::GetCurrentTime()); + } + + return result; +} + +bool Bot::CheckDisciplineReuseTimer(uint16 spell_id) +{ + ClearExpiredTimers(); + + if (GetDisciplineReuseTimer(spell_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer + return true; //can cast spell + } + + return false; +} + +void Bot::SetItemReuseTimer(uint32 item_id, uint32 reuse_timer) +{ + const auto *item = database.GetItem(item_id); + + if (!item) { + OwnerMessage("Failed to set item reuse timer."); + return; + } + + if (item->RecastDelay <= 0) { + return; + } + + if (CheckItemReuseTimer(item_id)) { + BotTimer_Struct t; + + t.timer_id = (item->RecastType == NegativeItemReuse ? item->ID : item->RecastType); + t.timer_value = ( + reuse_timer != 0 ? + (Timer::GetCurrentTime() + reuse_timer) : + (Timer::GetCurrentTime() + (item->RecastDelay * 1000)) + ); + t.recast_time = (reuse_timer != 0 ? reuse_timer : (item->RecastDelay * 1000)); + t.is_spell = false; + t.is_disc = false; + t.spell_id = 0; + t.is_item = true; + t.item_id = item->ID; + + bot_timers.push_back(t); + } + else { + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_item && + ( + ( + item->RecastType != 0 && + item->RecastType == bot_timers[i].timer_id + ) || + bot_timers[i].item_id == item_id + ) + ) { + bot_timers[i].timer_value = ( + reuse_timer != 0 ? + (Timer::GetCurrentTime() + reuse_timer) : + (Timer::GetCurrentTime() + (item->RecastDelay * 1000)) + ); + bot_timers[i].recast_time = ( + reuse_timer != 0 ? + reuse_timer : + (item->RecastDelay * 1000) + ); + break; + } + } + } + } +} + +uint32 Bot::GetItemReuseTimer(uint32 item_id) +{ + uint32 result = 0; + const EQ::ItemData* item; + + if (item_id) { + item = database.GetItem(item_id); + + if (!item) { + OwnerMessage("Failed to get item reuse timer."); + return result; + } + } + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_item && + ( + !item_id || + ( + ( + item->RecastType != 0 && + item->RecastType == bot_timers[i].timer_id + ) || + bot_timers[i].item_id == item_id + ) + ) + ) { + result = bot_timers[i].timer_value; + break; + } + } + } + + ClearExpiredTimers(); + + return result; +} + +bool Bot::CheckItemReuseTimer(uint32 item_id) +{ + ClearExpiredTimers(); + + if (GetItemReuseTimer(item_id) < Timer::GetCurrentTime()) { + return true; + } + + return false; +} + +uint32 Bot::GetItemReuseRemainingTime(uint32 item_id) +{ + uint32 result = 0; + + if (GetItemReuseTimer(item_id) > Timer::GetCurrentTime()) { + result = (GetItemReuseTimer(item_id) - Timer::GetCurrentTime()); + } + + return result; +} + +uint32 Bot::CalcSpellRecastTimer(uint16 spell_id) +{ + uint32 result = 0; + + if (spells[spell_id].recast_time == 0 && spells[spell_id].recovery_time == 0) { + return result; + } else { + if (spells[spell_id].recovery_time > spells[spell_id].recast_time) { + result = spells[spell_id].recovery_time; + } else { + result = spells[spell_id].recast_time; + } + } + + return result; +} + +void Bot::ClearDisciplineReuseTimer(uint16 spell_id) +{ + if (spell_id && !IsValidSpell(spell_id)) { + OwnerMessage( + fmt::format( + "{} is not a valid spell ID.'", + spell_id + ) + ); + return; + } + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if ( + bot_timers[i].is_disc && + bot_timers[i].timer_value >= Timer::GetCurrentTime() + ) { + if ( + !spell_id || + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[i].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) { + bot_timers[i].timer_value = 0; + } + } + } + } + + ClearExpiredTimers(); +} + +void Bot::ClearItemReuseTimer(uint32 item_id) +{ + const EQ::ItemData* item; + + if (item_id) { + item = database.GetItem(item_id); + + if (!item) { + OwnerMessage( + fmt::format( + "{} is not a valid item ID.", + item_id + ) + ); + return; + } + } + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if (bot_timers[i].is_item && bot_timers[i].timer_value >= Timer::GetCurrentTime()) { + if ( + !item_id || + ( + ( + item->RecastType != 0 && + item->RecastType == bot_timers[i].timer_id + ) || + bot_timers[i].item_id == item_id + ) + ) { + bot_timers[i].timer_value = 0; + } + } + } + } + + ClearExpiredTimers(); +} + +void Bot::ClearSpellRecastTimer(uint16 spell_id) +{ + if (spell_id && !IsValidSpell(spell_id)) { + OwnerMessage( + fmt::format( + "{} is not a valid spell ID.", + spell_id + ) + ); + return; + } + + if (!bot_timers.empty()) { + for (int i = 0; i < bot_timers.size(); i++) { + if (bot_timers[i].is_spell && bot_timers[i].timer_value >= Timer::GetCurrentTime()) { + if ( + !spell_id || + ( + ( + spells[spell_id].timer_id != 0 && + spells[spell_id].timer_id == bot_timers[i].timer_id + ) || + bot_timers[i].spell_id == spell_id + ) + ) { + bot_timers[i].timer_value = 0; + } + } + } + } + + ClearExpiredTimers(); +} + + +void Bot::ClearExpiredTimers() +{ + if (!bot_timers.empty()) { + int current = 0; + int end = bot_timers.size(); + + while (current < end) { + if (bot_timers[current].timer_value < Timer::GetCurrentTime()) { + bot_timers.erase(bot_timers.begin() + current); + } else { + current++; + } + + end = bot_timers.size(); + } + } +} + +void Bot::TryItemClick(uint16 slot_id) +{ + if (!GetOwner()) { + return; + } + + const auto *inst = GetClickItem(slot_id); + + if (!inst) { + return; + } + + const auto *item = inst->GetItem(); + + if (!item) { + return; + } + + if (!CheckItemReuseTimer(item->ID)) { + uint32 remaining_time = (GetItemReuseRemainingTime(item->ID) / 1000); + OwnerMessage( + fmt::format( + "I can use this item in {}.", + Strings::SecondsToTime(remaining_time) + ) + ); + return; + } + + DoItemClick(item, slot_id); +} + +EQ::ItemInstance *Bot::GetClickItem(uint16 slot_id) +{ + EQ::ItemInstance* inst = nullptr; + const EQ::ItemData* item = nullptr; + + inst = GetBotItem(slot_id); + + if (!inst || !inst->GetItem()) { + return nullptr; + } + + item = inst->GetItem(); + + if (item->ID == MAG_EPIC_1_0 && !RuleB(Bots, CanClickMageEpicV1)) { + OwnerMessage( + fmt::format( + "{} is currently disabled for bots to click.", + item->Name + ) + ); + return nullptr; + } + + if (item->Click.Effect <= 0) { + OwnerMessage( + fmt::format( + "{} does not have a clickable effect.", + item->Name + ) + ); + return nullptr; + } + + if (!IsValidSpell(item->Click.Effect)) { + OwnerMessage( + fmt::format( + "{} does not have a valid clickable effect.", + item->Name + ) + ); + return nullptr; + } + + if (item->ReqLevel > GetLevel()) { + OwnerMessage( + fmt::format( + "I am below the level requirement of {} for {}.", + item->ReqLevel, + item->Name + ) + ); + return nullptr; + } + + if (item->Click.Level2 > GetLevel()) { + OwnerMessage( + fmt::format( + "I must be level {} to use {}.", + item->Click.Level2, + item->Name + ) + ); + return nullptr; + } + + if (inst->GetCharges() == 0) { + OwnerMessage( + fmt::format( + "{} is out of charges.", + item->Name + ) + ); + return nullptr; + } + + return inst; +} + +void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id) +{ + bool is_casting_bard_song = false; + Mob* tar = (GetOwner()->GetTarget() ? GetOwner()->GetTarget() : this); + + if (IsCasting()) { + InterruptSpell(); + } + + SetIsUsingItemClick(true); + + BotGroupSay( + this, + fmt::format( + "Attempting to cast [{}] on {}.", + spells[item->Click.Effect].name, + tar->GetCleanName() + ).c_str() + ); + + if (!IsCastWhileInvisibleSpell(item->Click.Effect)) { + CommonBreakInvisible(); + } + + if (GetClass() == Class::Bard && IsCasting() && casting_spell_slot < EQ::spells::CastingSlot::MaxGems) { + is_casting_bard_song = true; + } + + if (GetClass() == Class::Bard) { + DoBardCastingFromItemClick(is_casting_bard_song, item->CastTime, item->Click.Effect, tar->GetID(), EQ::spells::CastingSlot::Item, slot_id, item->RecastType, item->RecastDelay); + } else { + if (!CastSpell(item->Click.Effect, tar->GetID(), EQ::spells::CastingSlot::Item, item->CastTime, 0, 0, slot_id)) { + OwnerMessage( + fmt::format( + "Casting failed for {}. This could be due to zone restrictions, target restrictions or other limiting factors.", + item->Name + ) + ); + } + } + +} + uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][Class::PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; diff --git a/zone/bot.h b/zone/bot.h index 7e62e0e16..38637f022 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -42,13 +42,12 @@ constexpr uint32 BOT_FOLLOW_DISTANCE_DEFAULT_MAX = 2500; // as DSq value (50 uni constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds +constexpr uint32 MAG_EPIC_1_0 = 28034; + extern WorldServer worldserver; constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this -constexpr int MaxSpellTimer = 15; -constexpr int MaxDisciplineTimer = 10; -constexpr int DisciplineReuseStart = MaxSpellTimer + 1; -constexpr int MaxTimer = MaxSpellTimer + MaxDisciplineTimer; +constexpr int NegativeItemReuse = -1; // Unlinked timer for items // nHSND negative Healer/Slower/Nuker/Doter // pH positive Healer @@ -222,6 +221,8 @@ public: void SetPullFlag(bool flag = true) { m_pull_flag = flag; } bool GetPullingFlag() const { return m_pulling_flag; } bool GetReturningFlag() const { return m_returning_flag; } + bool GetIsUsingItemClick() { return is_using_item_click; } + void SetIsUsingItemClick(bool flag = true) { is_using_item_click = flag; } bool UseDiscipline(uint32 spell_id, uint32 target); uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid); uint8 GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid); @@ -286,6 +287,10 @@ public: void SetEndurance(int32 newEnd) override; void DoEnduranceUpkeep(); + void TryItemClick(uint16 slot_id); + EQ::ItemInstance* GetClickItem(uint16 slot_id); + void DoItemClick(const EQ::ItemData* inst, uint16 slot_id); + bool AI_AddBotSpells(uint32 bot_spell_id); void AddSpellToBotList( int16 iPriority, @@ -408,11 +413,6 @@ public: static Bot* GetFirstBotInGroup(Group* group); static void ProcessClientZoneChange(Client* botOwner); static void ProcessBotOwnerRefDelete(Mob* botOwner); // Removes a Client* reference when the Client object is destroyed - static int32 GetSpellRecastTimer(Bot *caster, int timer_index); - static bool CheckSpellRecastTimers(Bot *caster, int SpellIndex); - static int32 GetDisciplineRecastTimer(Bot *caster, int timer_index); - static bool CheckDisciplineRecastTimers(Bot *caster, int timer_index); - static uint32 GetDisciplineRemainingTime(Bot *caster, int timer_index); //Raid methods static void ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite = false); @@ -612,8 +612,23 @@ public: _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); + uint32 GetSpellRecastTimer(uint16 spell_id = 0); + bool CheckSpellRecastTimer(uint16 spell_id = 0); + uint32 GetSpellRecastRemainingTime(uint16 spell_id = 0); + void SetSpellRecastTimer(uint16 spell_id, int32 recast_delay = 0); + uint32 CalcSpellRecastTimer(uint16 spell_id); + uint32 GetDisciplineReuseTimer(uint16 spell_id = 0); + bool CheckDisciplineReuseTimer(uint16 spell_id = 0); + uint32 GetDisciplineReuseRemainingTime(uint16 spell_id = 0); + void SetDisciplineReuseTimer(uint16 spell_id, int32 reuse_timer = 0); + uint32 GetItemReuseTimer(uint32 item_id = 0); + bool CheckItemReuseTimer(uint32 item_id = 0); + void SetItemReuseTimer(uint32 item_id, uint32 reuse_timer = 0); + void ClearDisciplineReuseTimer(uint16 spell_id = 0); + void ClearItemReuseTimer(uint32 item_id = 0); + void ClearSpellRecastTimer(uint16 spell_id = 0); + uint32 GetItemReuseRemainingTime(uint32 item_id = 0); + void ClearExpiredTimers(); void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} void SetShowHelm(bool showhelm) { _showhelm = showhelm; } void SetBeardColor(uint8 value) { beardcolor = value; } @@ -713,7 +728,8 @@ public: // New accessors for BotDatabase access bool DeleteBot(); - uint32* GetTimers() { return timers; } + std::vector GetBotTimers() { return bot_timers; } + void SetBotTimers(std::vector timers) { bot_timers = timers; } uint32 GetLastZoneID() const { return _lastZoneId; } int32 GetBaseAC() const { return _baseAC; } int32 GetBaseATK() const { return _baseATK; } @@ -828,6 +844,7 @@ protected: std::vector AIBot_spells; std::vector AIBot_spells_enforced; + std::vector bot_timers; private: // Class Members @@ -861,7 +878,6 @@ private: int32 cur_end; int32 max_end; int32 end_regen; - uint32 timers[MaxTimer]; Timer m_evade_timer; // can be moved to pTimers at some point Timer m_alt_combat_hate_timer; @@ -875,6 +891,7 @@ private: bool m_pull_flag; bool m_pulling_flag; bool m_returning_flag; + bool is_using_item_click; eStandingPetOrder m_previous_pet_order; uint32 m_bot_caster_range; BotCastingRoles m_CastingRoles; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 9bcc1ec79..544a1bc14 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1375,6 +1375,7 @@ int bot_command_init(void) 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("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || @@ -1444,6 +1445,7 @@ int bot_command_init(void) bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", AccountStatus::Player, bot_command_summon_corpse) || bot_command_add("suspend", "Suspends a bot's AI processing until released", AccountStatus::Player, bot_command_suspend) || bot_command_add("taunt", "Toggles taunt use by a bot", AccountStatus::Player, bot_command_taunt) || + bot_command_add("timer", "Checks or clears timers of the chosen type.", AccountStatus::GMMgmt, bot_command_timer) || bot_command_add("track", "Orders a capable bot to track enemies", AccountStatus::Player, bot_command_track) || bot_command_add("viewcombos", "Views bot race class combinations", AccountStatus::Player, bot_command_view_combos) || bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", AccountStatus::Player, bot_command_water_breathing) @@ -2832,7 +2834,7 @@ void bot_command_aggressive(Client *c, const Seperator *sep) } } - c->Message(Chat::White, "%i of %i bots have used aggressive disciplines", success_count, candidate_count); + c->Message(Chat::White, "%i of %i bots have attempted to use aggressive disciplines", success_count, candidate_count); } void bot_command_apply_poison(Client *c, const Seperator *sep) @@ -3332,7 +3334,7 @@ void bot_command_defensive(Client *c, const Seperator *sep) } } - c->Message(Chat::White, "%i of %i bots have used defensive disciplines", success_count, candidate_count); + c->Message(Chat::White, "%i of %i bots have attempted to use defensive disciplines", success_count, candidate_count); } void bot_command_depart(Client *c, const Seperator *sep) @@ -5286,6 +5288,194 @@ void bot_command_taunt(Client *c, const Seperator *sep) } } +void bot_command_timer(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] [actionable].", sep->arg[0]); + c->Message(Chat::White, "When setting, you can leave the value blank to use the default for the item or specify a value in ms to set the timer to."); + c->Message(Chat::White, "Returns or sets the provided timer(s) for the selected bot(s) or clears the selected timer(s) for the selected bot(s)."); + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + std::string arg3 = sep->arg[3]; + int ab_arg = 4; + bool clear = false; + bool has = false; + bool set = false; + bool disc = false; + bool item = false; + bool spell = false; + uint32 timer_id = 0; + uint32 timer_value = 0; + bool all = false; + + if (!arg1.compare("clear")) { + clear = true; + } + else if (!arg1.compare("has")) { + has = true; + } + else if (!arg1.compare("set")) { + set = true; + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (!arg2.compare("disc")) { + disc = true; + } + else if (!arg2.compare("item")) { + item = true; + } + else if (!arg2.compare("spell")) { + spell = true; + } + else { + c->Message(Chat::White, "Incorrect timer type, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (sep->IsNumber(3)) { + timer_id = atoi(sep->arg[3]); + if (timer_id < 0) { + c->Message(Chat::White, "You cannot use negative numbers."); + return; + } + } + else if (!arg3.compare("all")) { + if (has || set) { + c->Message(Chat::White, "You can only use 'all' for clearing timers."); + return; + } + + all = true; + } + else { + c->Message(Chat::White, "Incorrect ID option, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (set) { + if (sep->IsNumber(4)) { + ab_arg = 5; + timer_value = atoi(sep->arg[4]); + if (timer_value <= 0) { + c->Message(Chat::White, "You cannot use 0 or negative numbers."); + return; + } + } + } + + 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, sep->arg[ab_arg], 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); + + for (auto my_bot : sbl) { + bool found = false; + + if (clear) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'Clearing {} timer{}'", + my_bot->GetCleanName(), + disc ? "Discipline" : item ? "Item" : "Spell", + (all ? "s." : ".") + ).c_str() + ); + + if (disc) { + my_bot->ClearDisciplineReuseTimer(timer_id); + } + else if (item) { + my_bot->ClearItemReuseTimer(timer_id); + } + else if (spell) { + my_bot->ClearSpellRecastTimer(timer_id); + } + } + else if (has) { + uint32 remaining_time; + std::string time_string = ""; + + if (disc) { + if (!my_bot->CheckDisciplineReuseTimer(timer_id)) { + remaining_time = my_bot->GetDisciplineReuseRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + else if (item) { + if (!my_bot->CheckItemReuseTimer(timer_id)) { + remaining_time = my_bot->GetItemReuseRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + else if (spell) { + if (!my_bot->CheckSpellRecastTimer(timer_id)) { + remaining_time = my_bot->GetSpellRecastRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + + c->Message( + Chat::White, + fmt::format( + "{} says, 'I {}{}{}'", + my_bot->GetCleanName(), + (!found ? " do not have that timer currently" : " have "), + (!found ? "" : time_string), + (!found ? "." : " remaining.") + ).c_str() + ); + } + else if (set) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'Setting {} timer{} for {} to {}.'", + my_bot->GetCleanName(), + disc ? "Discipline" : item ? "Item" : "Spell", + (all ? "s" : ""), + timer_id, + timer_value ? std::to_string(timer_value) : "the default value" + ).c_str() + ); + + if (disc) { + my_bot->ClearDisciplineReuseTimer(timer_id); + my_bot->SetDisciplineReuseTimer(timer_id, timer_value); + } + else if (item) { + my_bot->ClearItemReuseTimer(timer_id); + my_bot->SetItemReuseTimer(timer_id, timer_value); + } + else if (spell) { + my_bot->ClearSpellRecastTimer(timer_id); + my_bot->SetSpellRecastTimer(timer_id, timer_value); + } + } + } +} + void bot_command_track(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_command_track", sep->arg[0], "track")) @@ -10530,6 +10720,61 @@ void bot_command_caster_range(Client* c, const Seperator* sep) } } +void bot_command_click_item(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, BotsCanClickItems)) { + c->Message(Chat::White, "The ability for bots to click equipped items is currently disabled."); + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "This will cause the selected bots to click the item in the given slot ID."); + c->Message(Chat::White, "Use ^invlist to see their items along with slot IDs."); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::Yellow, "You must specify a slot ID. Use %s help for more information.", sep->arg[0]); + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + + int ab_arg = 1; + uint32 slot_id = 0; + + if (sep->IsNumber(1)) { + ab_arg = 2; + slot_id = atoi(sep->arg[1]); + if (slot_id < EQ::invslot::EQUIPMENT_BEGIN || slot_id > EQ::invslot::EQUIPMENT_END) { + c->Message(Chat::Yellow, "You must specify a valid inventory slot from 0 to 22. Use %s help for more information", sep->arg[0]); + return; + } + } + + 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, sep->arg[ab_arg], 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); + + for (auto my_bot : sbl) { + if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { + c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); + continue; + } + + my_bot->TryItemClick(slot_id); + } +} + void bot_command_pickpocket(Client *c, const Seperator *sep) { if (helper_command_disabled(c, RuleB(Bots, AllowPickpocketCommand), "pickpocket")) { diff --git a/zone/bot_command.h b/zone/bot_command.h index 2e2d9cc59..28bd74daa 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -554,6 +554,7 @@ void bot_command_bind_affinity(Client *c, const Seperator *sep); void bot_command_bot(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_click_item(Client* c, const Seperator* sep); void bot_command_cure(Client *c, const Seperator *sep); void bot_command_defensive(Client *c, const Seperator *sep); void bot_command_depart(Client *c, const Seperator *sep); @@ -595,6 +596,7 @@ void bot_command_enforce_spell_list(Client* c, const Seperator* sep); void bot_command_summon_corpse(Client *c, const Seperator *sep); void bot_command_suspend(Client *c, const Seperator *sep); void bot_command_taunt(Client *c, const Seperator *sep); +void bot_command_timer(Client* c, const Seperator* sep); void bot_command_track(Client *c, const Seperator *sep); void bot_command_view_combos(Client *c, const Seperator *sep); void bot_command_water_breathing(Client *c, const Seperator *sep); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 43f7a6ede..09f49f292 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -24,6 +24,7 @@ #include "../common/repositories/bot_data_repository.h" #include "../common/repositories/bot_inventories_repository.h" +#include "../common/repositories/bot_timers_repository.h" #include "zonedb.h" #include "bot.h" @@ -917,45 +918,39 @@ bool BotDatabase::LoadTimers(Bot* bot_inst) if (!bot_inst) return false; - query = StringFormat( - "SELECT" - " IfNull(bt.`timer_id`, '0') As timer_id," - " IfNull(bt.`timer_value`, '0') As timer_value," - " IfNull(MAX(sn.`recast_time`), '0') AS MaxTimer" - " FROM `bot_timers` bt, `spells_new` sn" - " WHERE bt.`bot_id` = '%u' AND sn.`EndurTimerIndex` = (" - "SELECT case" - " WHEN timer_id > '%i' THEN timer_id - '%i'" - " ELSE timer_id END AS timer_id" - " FROM `bot_timers` WHERE `timer_id` = bt.`timer_id` AND `bot_id` = bt.`bot_id`" // double-check validity - ")" - " AND sn.`classes%i` <= '%i'", - bot_inst->GetBotID(), - (DisciplineReuseStart - 1), - (DisciplineReuseStart - 1), - bot_inst->GetClass(), - bot_inst->GetLevel() + auto timers = BotTimersRepository::GetWhere( + database, + fmt::format("bot_id = {}", bot_inst->GetBotID()) ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - if (!results.RowCount()) - return true; - uint32* bot_timers = bot_inst->GetTimers(); - if (!bot_timers) - return false; + std::vector bot_timers; - int timer_id = 0; - uint32 timer_value = 0; - uint32 max_value = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - timer_id = Strings::ToInt(row[0]) - 1; - timer_value = Strings::ToInt(row[1]); - max_value = Strings::ToInt(row[2]); + BotTimer_Struct t{}; + t.timer_id = 0; + t.timer_value = 0; + t.recast_time = 0; + t.is_spell = false; + t.is_disc = false; + t.spell_id = 0; + t.is_item = false; + t.item_id = 0; - if (timer_id >= 0 && timer_id < MaxTimer && timer_value < (Timer::GetCurrentTime() + max_value)) - bot_timers[timer_id] = timer_value; + for (auto& timer : timers) { + if (t.timer_value < (Timer::GetCurrentTime() + t.recast_time)) { + t.timer_id = timer.timer_id; + t.timer_value = timer.timer_value; + t.recast_time = timer.recast_time; + t.is_spell = timer.is_spell ? true : false; + t.is_disc = timer.is_disc ? true : false; + t.spell_id = timer.spell_id; + t.is_item = timer.is_item ? true : false; + t.item_id = timer.item_id; + bot_timers.push_back(t); + } + } + + if (!bot_timers.empty()) { + bot_inst->SetBotTimers(bot_timers); } return true; @@ -963,26 +958,56 @@ bool BotDatabase::LoadTimers(Bot* bot_inst) bool BotDatabase::SaveTimers(Bot* bot_inst) { - if (!bot_inst) + if (!bot_inst) { return false; + } - if (!DeleteTimers(bot_inst->GetBotID())) + if (!DeleteTimers(bot_inst->GetBotID())) { return false; + } - uint32* bot_timers = bot_inst->GetTimers(); - if (!bot_timers) - return false; + std::vector bot_timers = bot_inst->GetBotTimers(); - for (int timer_index = 0; timer_index < MaxTimer; ++timer_index) { - if (bot_timers[timer_index] <= Timer::GetCurrentTime()) - continue; + if (bot_timers.empty()) { + return true; + } - query = fmt::format( - "REPLACE INTO `bot_timers` (`bot_id`, `timer_id`, `timer_value`) VALUES ('{}', '{}', '{}')", - bot_inst->GetBotID(), (timer_index + 1), bot_timers[timer_index] + std::vector timers; + + if (!bot_timers.empty()) { + for (auto & bot_timer : bot_timers) { + if (bot_timer.timer_value <= Timer::GetCurrentTime()) { + continue; + } + + auto t = BotTimersRepository::BotTimers{ + .bot_id = bot_inst->GetBotID(), + .timer_id = bot_timer.timer_id, + .timer_value = bot_timer.timer_value, + .recast_time = bot_timer.recast_time, + .is_spell = bot_timer.is_spell ? true : false, + .is_disc = bot_timer.is_disc ? true : false, + .spell_id = bot_timer.spell_id, + .is_item = bot_timer.is_item ? true : false, + .item_id = bot_timer.item_id + }; + + timers.push_back(t); + } + + if (timers.empty()) { + return true; + } + + // delete existing + BotTimersRepository::DeleteWhere( + database, + fmt::format("bot_id = {}", bot_inst->GetBotID()) ); - auto results = database.QueryDatabase(query); - if (!results.Success()) { + + // bulk insert current + auto success = BotTimersRepository::InsertMany(database, timers); + if (!success) { DeleteTimers(bot_inst->GetBotID()); return false; } @@ -993,13 +1018,14 @@ bool BotDatabase::SaveTimers(Bot* bot_inst) bool BotDatabase::DeleteTimers(const uint32 bot_id) { - if (!bot_id) + if (!bot_id) { return false; + } - query = StringFormat("DELETE FROM `bot_timers` WHERE `bot_id` = '%u'", bot_id); - auto results = database.QueryDatabase(query); - if (!results.Success()) + auto success = BotTimersRepository::DeleteWhere(database, fmt::format("bot_id = {}", bot_id)); + if (!success) { return false; + } return true; } diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 5264c4ef9..ca225f8b4 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -81,4 +81,15 @@ struct BotSpells_Struct { uint8 bucket_comparison; }; +struct BotTimer_Struct { + uint32 timer_id; + uint32 timer_value; + uint32 recast_time; + bool is_spell; + bool is_disc; + uint16 spell_id; + bool is_item; + uint32 item_id; +}; + #endif // BOT_STRUCTS diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index ba2039fc0..617c31d60 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -108,7 +108,7 @@ bool Bot::BotCastSong(Mob* tar, uint8 botLevel) { auto iter : botSongList) { if (!iter.SpellId) continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + if (!CheckSpellRecastTimer(iter.SpellId)) continue; if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) continue; @@ -142,7 +142,7 @@ bool Bot::BotCastCombatSong(Mob* tar, uint8 botLevel) { auto iter : botSongList) { if (!iter.SpellId) continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + if (!CheckSpellRecastTimer(iter.SpellId)) continue; if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) continue; @@ -174,7 +174,7 @@ bool Bot::BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpel for (auto iter : botSongList) { if (!iter.SpellId) continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + if (!CheckSpellRecastTimer(iter.SpellId)) continue; if (!IsSpellUsableInThisZoneType(iter.SpellId, zone->GetZoneType())) continue; @@ -312,7 +312,7 @@ bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe continue; } - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) { + if (!CheckSpellRecastTimer(iter.SpellId)) { continue; } @@ -403,7 +403,7 @@ bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const b continue; } - if (CheckSpellRecastTimers(this, s.SpellIndex)) + if (CheckSpellRecastTimer(s.SpellId)) { if (!(!tar->IsImmuneToSpell(s.SpellId, this) && tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { @@ -438,7 +438,7 @@ bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const b continue; } - if (CheckSpellRecastTimers(this, s.SpellIndex)) { + if (CheckSpellRecastTimer(s.SpellId)) { if (!(!tar->IsImmuneToSpell(s.SpellId, this) && tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { @@ -652,7 +652,7 @@ bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) { } } - if (CheckSpellRecastTimers(this, s.SpellIndex)) { + if (CheckSpellRecastTimer(s.SpellId)) { uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { @@ -947,7 +947,7 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) { } } - if (CheckSpellRecastTimers(this, s.SpellIndex)) + if (CheckSpellRecastTimer(s.SpellId)) { uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); @@ -1306,10 +1306,8 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain SetMana(hasMana); } else { - AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time; - - if (spells[AIBot_spells[i].spellid].timer_id > 0) { - SetSpellRecastTimer(spells[AIBot_spells[i].spellid].timer_id, spells[AIBot_spells[i].spellid].recast_time); + if (CalcSpellRecastTimer(AIBot_spells[i].spellid) > 0) { + SetSpellRecastTimer(AIBot_spells[i].spellid); } } @@ -2044,7 +2042,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) { continue; } - if ((botSpellList[i].type & spellType) && CheckSpellRecastTimers(botCaster, i)) { + if ((botSpellList[i].type & spellType) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -2069,7 +2067,7 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { for (auto botSpellListItr : botSpellList) { // 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) && botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId)) { result.SpellId = botSpellListItr.SpellId; result.SpellIndex = botSpellListItr.SpellIndex; result.ManaCost = botSpellListItr.ManaCost; @@ -2106,7 +2104,7 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { if ( botSpellList[i].spellid == botSpellListItr.SpellId && (botSpellList[i].type & SpellType_Heal) && - CheckSpellRecastTimers(botCaster, botSpellListItr.SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr.SpellId) ) { result.SpellId = botSpellListItr.SpellId; result.SpellIndex = botSpellListItr.SpellIndex; @@ -2139,7 +2137,7 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) { continue; } - if (IsCompleteHealSpell(botSpellList[i].spellid) && CheckSpellRecastTimers(botCaster, i)) { + if (IsCompleteHealSpell(botSpellList[i].spellid) && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -2163,7 +2161,7 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) { 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) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2192,7 +2190,7 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) { IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || IsFastHealSpell(botSpellListItr->SpellId) ) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2219,7 +2217,7 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) { // Assuming all the spells have been loaded into this list by level and in descending order if ( IsRegularGroupHealSpell(botSpellListItr->SpellId) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2257,7 +2255,7 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) { if ( botSpellList[i].spellid == botSpellListItr->SpellId && (botSpellList[i].type & SpellType_Heal) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2287,7 +2285,7 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) { // Assuming all the spells have been loaded into this list by level and in descending order if ( IsGroupCompleteHealSpell(botSpellListItr->SpellId) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2314,7 +2312,7 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) { // Assuming all the spells have been loaded into this list by level and in descending order if ( IsMesmerizeSpell(botSpellListItr->SpellId) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2343,7 +2341,7 @@ BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) { if ( IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2371,7 +2369,7 @@ BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) { if ( IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId) ) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2437,7 +2435,7 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot *botCaster) { 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 (IsSummonPetSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if (IsSummonPetSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { if (!strncmp(spells[botSpellListItr->SpellId].teleport_zone, petType.c_str(), petType.length())) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2546,7 +2544,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType 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 ((IsPureNukeSpell(botSpellListItr->SpellId) || IsDamageSpell(botSpellListItr->SpellId)) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if ((IsPureNukeSpell(botSpellListItr->SpellId) || IsDamageSpell(botSpellListItr->SpellId)) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; result.ManaCost = botSpellListItr->ManaCost; @@ -2574,7 +2572,7 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType 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 (IsStunSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) + if (IsStunSpell(botSpellListItr->SpellId) && botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { result.SpellId = botSpellListItr->SpellId; result.SpellIndex = botSpellListItr->SpellIndex; @@ -2614,7 +2612,7 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* targ // Assuming all the spells have been loaded into this list by level and in descending order bool spellSelected = false; - if (CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { + if (botCaster->CheckSpellRecastTimer(botSpellListItr->SpellId)) { if (selectLureNuke && (spells[botSpellListItr->SpellId].resist_difficulty < lureResisValue)) { spellSelected = true; } @@ -2682,7 +2680,7 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) { if (((botSpellList[i].type & SpellType_Debuff) || IsDebuffSpell(botSpellList[i].spellid)) && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) && tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) - && CheckSpellRecastTimers(botCaster, i)) { + && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -2735,7 +2733,7 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { || (needsDiseaseResistDebuff && (IsEffectInSpell(botSpellList[i].spellid, SE_ResistDisease)) || IsEffectInSpell(botSpellList[i].spellid, SE_ResistAll))) && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) && tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) - && CheckSpellRecastTimers(botCaster, i)) { + && botCaster->CheckSpellRecastTimer(botSpellList[i].spellid)) { result.SpellId = botSpellList[i].spellid; result.SpellIndex = i; result.ManaCost = botSpellList[i].manacost; @@ -2786,31 +2784,31 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { for (std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { BotSpell selectedBotSpell = *itr; - if (IsGroupSpell(itr->SpellId) && CheckSpellRecastTimers(botCaster, itr->SpellIndex)) { + if (IsGroupSpell(itr->SpellId) && botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { if (selectedBotSpell.SpellId == 0) continue; - if (isPoisoned && IsEffectInSpell(itr->SpellId, SE_PoisonCounter)) { + if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { spellSelected = true; } - else if (isDiseased && IsEffectInSpell(itr->SpellId, SE_DiseaseCounter)) { + else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { spellSelected = true; } - else if (isCursed && IsEffectInSpell(itr->SpellId, SE_CurseCounter)) { + else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { spellSelected = true; } - else if (isCorrupted && IsEffectInSpell(itr->SpellId, SE_CorruptionCounter)) { + else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { spellSelected = true; } - else if (IsEffectInSpell(itr->SpellId, SE_DispelDetrimental)) { + else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { spellSelected = true; } if (spellSelected) { - result.SpellId = itr->SpellId; - result.SpellIndex = itr->SpellIndex; - result.ManaCost = itr->ManaCost; + result.SpellId = selectedBotSpell.SpellId; + result.SpellIndex = selectedBotSpell.SpellIndex; + result.ManaCost = selectedBotSpell.ManaCost; break; } @@ -2823,31 +2821,31 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { for(std::list::iterator itr = cureList.begin(); itr != cureList.end(); ++itr) { BotSpell selectedBotSpell = *itr; - if (CheckSpellRecastTimers(botCaster, itr->SpellIndex)) { + if (botCaster->CheckSpellRecastTimer(selectedBotSpell.SpellId)) { if (selectedBotSpell.SpellId == 0) continue; - if (isPoisoned && IsEffectInSpell(itr->SpellId, SE_PoisonCounter)) { + if (isPoisoned && IsEffectInSpell(selectedBotSpell.SpellId, SE_PoisonCounter)) { spellSelected = true; } - else if (isDiseased && IsEffectInSpell(itr->SpellId, SE_DiseaseCounter)) { + else if (isDiseased && IsEffectInSpell(selectedBotSpell.SpellId, SE_DiseaseCounter)) { spellSelected = true; } - else if (isCursed && IsEffectInSpell(itr->SpellId, SE_CurseCounter)) { + else if (isCursed && IsEffectInSpell(selectedBotSpell.SpellId, SE_CurseCounter)) { spellSelected = true; } - else if (isCorrupted && IsEffectInSpell(itr->SpellId, SE_CorruptionCounter)) { + else if (isCorrupted && IsEffectInSpell(selectedBotSpell.SpellId, SE_CorruptionCounter)) { spellSelected = true; } - else if (IsEffectInSpell(itr->SpellId, SE_DispelDetrimental)) { + else if (IsEffectInSpell(selectedBotSpell.SpellId, SE_DispelDetrimental)) { spellSelected = true; } if (spellSelected) { - result.SpellId = itr->SpellId; - result.SpellIndex = itr->SpellIndex; - result.ManaCost = itr->ManaCost; + result.SpellId = selectedBotSpell.SpellId; + result.SpellIndex = selectedBotSpell.SpellIndex; + result.ManaCost = selectedBotSpell.ManaCost; break; } @@ -2859,69 +2857,6 @@ BotSpell Bot::GetBestBotSpellForCure(Bot* botCaster, Mob *tar) { return result; } -void Bot::SetSpellRecastTimer(int timer_index, int32 recast_delay) { - if (timer_index > 0 && timer_index <= MaxSpellTimer) { - timers[timer_index - 1] = Timer::GetCurrentTime() + recast_delay; - } -} - -int32 Bot::GetSpellRecastTimer(Bot *caster, int timer_index) { - int32 result = 0; - if (caster) { - if (timer_index > 0 && timer_index <= MaxSpellTimer) { - result = caster->timers[timer_index - 1]; - } - } - return result; -} - -bool Bot::CheckSpellRecastTimers(Bot *caster, int SpellIndex) { - if (caster) { - if (caster->AIBot_spells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast - if (GetSpellRecastTimer(caster, spells[caster->AIBot_spells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer - return true; //can cast spell - } - } - } - return false; -} - -void Bot::SetDisciplineRecastTimer(int timer_index, int32 recast_delay) { - if (timer_index > 0 && timer_index <= MaxDisciplineTimer) { - timers[DisciplineReuseStart + timer_index - 1] = Timer::GetCurrentTime() + recast_delay; - } -} - -int32 Bot::GetDisciplineRecastTimer(Bot *caster, int timer_index) { - int32 result = 0; - if (caster) { - if (timer_index > 0 && timer_index <= MaxDisciplineTimer) { - result = caster->timers[DisciplineReuseStart + timer_index - 1]; - } - } - return result; -} - -uint32 Bot::GetDisciplineRemainingTime(Bot *caster, int timer_index) { - int32 result = 0; - if (caster) { - if (timer_index > 0 && timer_index <= MaxDisciplineTimer) { - if (GetDisciplineRecastTimer(caster, timer_index) > Timer::GetCurrentTime()) - result = GetDisciplineRecastTimer(caster, timer_index) - Timer::GetCurrentTime(); - } - } - return result; -} - -bool Bot::CheckDisciplineRecastTimers(Bot *caster, int timer_index) { - if (caster) { - if (GetDisciplineRecastTimer(caster, timer_index) < Timer::GetCurrentTime()) { //checks for spells on the same timer - return true; //can cast spell - } - } - return false; -} - uint8 Bot::GetChanceToCastBySpellType(uint32 spellType) { uint8 spell_type_index = SPELL_TYPE_COUNT; diff --git a/zone/lua_bot.cpp b/zone/lua_bot.cpp index 843b631e5..ccb174ccd 100644 --- a/zone/lua_bot.cpp +++ b/zone/lua_bot.cpp @@ -389,6 +389,96 @@ int Lua_Bot::GetRawItemAC() { return self->GetRawItemAC(); } +void Lua_Bot::ClearDisciplineReuseTimer() { + Lua_Safe_Call_Void(); + return self->ClearDisciplineReuseTimer(); +} + +void Lua_Bot::ClearDisciplineReuseTimer(uint16 spell_id) { + Lua_Safe_Call_Void(); + return self->ClearDisciplineReuseTimer(spell_id); +} + +void Lua_Bot::ClearItemReuseTimer() { + Lua_Safe_Call_Void(); + return self->ClearItemReuseTimer(); +} + +void Lua_Bot::ClearItemReuseTimer(uint32 item_id) { + Lua_Safe_Call_Void(); + return self->ClearItemReuseTimer(item_id); +} + +void Lua_Bot::ClearSpellRecastTimer() { + Lua_Safe_Call_Void(); + return self->ClearSpellRecastTimer(); +} + +void Lua_Bot::ClearSpellRecastTimer(uint16 spell_id) { + Lua_Safe_Call_Void(); + return self->ClearSpellRecastTimer(spell_id); +} + +uint32 Lua_Bot::GetDisciplineReuseTimer() { + Lua_Safe_Call_Int(); + return self->GetDisciplineReuseRemainingTime(); +} + +uint32 Lua_Bot::GetDisciplineReuseTimer(uint16 spell_id) { + Lua_Safe_Call_Int(); + return self->GetDisciplineReuseRemainingTime(spell_id); +} + +uint32 Lua_Bot::GetItemReuseTimer() { + Lua_Safe_Call_Int(); + return self->GetItemReuseRemainingTime(); +} + +uint32 Lua_Bot::GetItemReuseTimer(uint32 item_id) { + Lua_Safe_Call_Int(); + return self->GetItemReuseRemainingTime(item_id); +} + +uint32 Lua_Bot::GetSpellRecastTimer() { + Lua_Safe_Call_Int(); + return self->GetSpellRecastRemainingTime(); +} + +uint32 Lua_Bot::GetSpellRecastTimer(uint16 spell_id) { + Lua_Safe_Call_Int(); + return self->GetSpellRecastRemainingTime(spell_id); +} + +void Lua_Bot::SetDisciplineReuseTimer(uint16 spell_id) { + Lua_Safe_Call_Void(); + return self->SetDisciplineReuseTimer(spell_id); +} + +void Lua_Bot::SetDisciplineReuseTimer(uint16 spell_id, uint32 reuse_timer) { + Lua_Safe_Call_Void(); + return self->SetDisciplineReuseTimer(spell_id, reuse_timer); +} + +void Lua_Bot::SetItemReuseTimer(uint32 item_id) { + Lua_Safe_Call_Void(); + return self->SetItemReuseTimer(item_id); +} + +void Lua_Bot::SetItemReuseTimer(uint32 item_id, uint32 reuse_timer) { + Lua_Safe_Call_Void(); + return self->SetItemReuseTimer(item_id, reuse_timer); +} + +void Lua_Bot::SetSpellRecastTimer(uint16 spell_id) { + Lua_Safe_Call_Void(); + return self->SetSpellRecastTimer(spell_id); +} + +void Lua_Bot::SetSpellRecastTimer(uint16 spell_id, uint32 recast_delay) { + Lua_Safe_Call_Void(); + return self->SetSpellRecastTimer(spell_id, recast_delay); +} + bool Lua_Bot::IsGrouped() { Lua_Safe_Call_Bool(); return self->IsGrouped(); @@ -600,6 +690,12 @@ luabind::scope lua_register_bot() { .def("ApplySpellRaid", (void(Lua_Bot::*)(int,int,int,bool,bool))&Lua_Bot::ApplySpellRaid) .def("Camp", (void(Lua_Bot::*)(void))&Lua_Bot::Camp) .def("Camp", (void(Lua_Bot::*)(bool))&Lua_Bot::Camp) + .def("ClearDisciplineReuseTimer", (void(Lua_Bot::*)())&Lua_Bot::ClearDisciplineReuseTimer) + .def("ClearDisciplineReuseTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::ClearDisciplineReuseTimer) + .def("ClearItemReuseTimer", (void(Lua_Bot::*)())&Lua_Bot::ClearItemReuseTimer) + .def("ClearItemReuseTimer", (void(Lua_Bot::*)(uint32))&Lua_Bot::ClearItemReuseTimer) + .def("ClearSpellRecastTimer", (void(Lua_Bot::*)())&Lua_Bot::ClearSpellRecastTimer) + .def("ClearSpellRecastTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::ClearSpellRecastTimer) .def("CountBotItem", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountBotItem) .def("CountItemEquippedByID", (int(Lua_Bot::*)(uint32))&Lua_Bot::CountItemEquippedByID) .def("DeleteBucket", (void(Lua_Bot::*)(std::string))&Lua_Bot::DeleteBucket) @@ -623,6 +719,8 @@ luabind::scope lua_register_bot() { .def("GetBotID", (uint32(Lua_Bot::*)(void))&Lua_Bot::GetBotID) .def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItem) .def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItemIDBySlot) + .def("GetDisciplineReuseTimer", (uint32(Lua_Bot::*)())&Lua_Bot::GetDisciplineReuseTimer) + .def("GetDisciplineReuseTimer", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetDisciplineReuseTimer) .def("GetBucket", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucket) .def("GetBucketExpires", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucketExpires) .def("GetBucketRemaining", (std::string(Lua_Bot::*)(std::string))&Lua_Bot::GetBucketRemaining) @@ -633,13 +731,17 @@ luabind::scope lua_register_bot() { .def("GetInstrumentMod", (int(Lua_Bot::*)(int))&Lua_Bot::GetInstrumentMod) .def("GetItemAt", (Lua_ItemInst(Lua_Bot::*)(int16))&Lua_Bot::GetItemAt) .def("GetItemIDAt", (int(Lua_Bot::*)(int16))&Lua_Bot::GetItemIDAt) + .def("GetItemReuseTimer", (uint32(Lua_Bot::*)())&Lua_Bot::GetItemReuseTimer) + .def("GetItemReuseTimer", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::GetItemReuseTimer) .def("GetOwner", (Lua_Mob(Lua_Bot::*)(void))&Lua_Bot::GetOwner) .def("GetRaceAbbreviation", (std::string(Lua_Bot::*)(void))&Lua_Bot::GetRaceAbbreviation) .def("GetRawItemAC", (int(Lua_Bot::*)(void))&Lua_Bot::GetRawItemAC) .def("GetSpellDamage", (int(Lua_Bot::*)(void))&Lua_Bot::GetSpellDamage) + .def("GetSpellRecastTimer", (uint32(Lua_Bot::*)())&Lua_Bot::GetSpellRecastTimer) + .def("GetSpellRecastTimer", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetSpellRecastTimer) .def("HasAugmentEquippedByID", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasAugmentEquippedByID) .def("HasBotItem", (int16(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem) - .def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16)) & Lua_Bot::HasBotSpellEntry) + .def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16))&Lua_Bot::HasBotSpellEntry) .def("HasItemEquippedByID", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasItemEquippedByID) .def("IsGrouped", (bool(Lua_Bot::*)(void))&Lua_Bot::IsGrouped) .def("IsSitting", (bool(Lua_Bot::*)(void))&Lua_Bot::IsSitting) @@ -655,6 +757,10 @@ luabind::scope lua_register_bot() { .def("SetBucket", (void(Lua_Bot::*)(std::string,std::string,std::string))&Lua_Bot::SetBucket) .def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask) .def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask) + .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::SetDisciplineReuseTimer) + .def("SetDisciplineReuseTimer", (void(Lua_Bot::*)(uint16, uint32))&Lua_Bot::SetDisciplineReuseTimer) + .def("SetItemReuseTimer", (void(Lua_Bot::*)(uint32))&Lua_Bot::SetItemReuseTimer) + .def("SetItemReuseTimer", (void(Lua_Bot::*)(uint32, uint32))&Lua_Bot::SetItemReuseTimer) .def("SetSpellDuration", (void(Lua_Bot::*)(int))&Lua_Bot::SetSpellDuration) .def("SetSpellDuration", (void(Lua_Bot::*)(int,int))&Lua_Bot::SetSpellDuration) .def("SetSpellDuration", (void(Lua_Bot::*)(int,int,int))&Lua_Bot::SetSpellDuration) @@ -668,6 +774,8 @@ luabind::scope lua_register_bot() { .def("SetSpellDurationRaid", (void(Lua_Bot::*)(int,int,int))&Lua_Bot::SetSpellDurationRaid) .def("SetSpellDurationRaid", (void(Lua_Bot::*)(int,int,int,bool))&Lua_Bot::SetSpellDurationRaid) .def("SetSpellDurationRaid", (void(Lua_Bot::*)(int,int,int,bool,bool))&Lua_Bot::SetSpellDurationRaid) + .def("SetSpellRecastTimer", (void(Lua_Bot::*)(uint16))&Lua_Bot::SetSpellRecastTimer) + .def("SetSpellRecastTimer", (void(Lua_Bot::*)(uint16, uint32))&Lua_Bot::SetSpellRecastTimer) .def("SendPayload", (void(Lua_Bot::*)(int))&Lua_Bot::SendPayload) .def("SendPayload", (void(Lua_Bot::*)(int,std::string))&Lua_Bot::SendPayload) .def("Signal", (void(Lua_Bot::*)(int))&Lua_Bot::Signal) diff --git a/zone/lua_bot.h b/zone/lua_bot.h index d67e9166f..34960ce61 100644 --- a/zone/lua_bot.h +++ b/zone/lua_bot.h @@ -107,6 +107,25 @@ public: void SetSpellDurationRaid(int spell_id, int duration, int level, bool allow_pets); void SetSpellDurationRaid(int spell_id, int duration, int level, bool allow_pets, bool is_raid_group_only); + void ClearDisciplineReuseTimer(); + void ClearDisciplineReuseTimer(uint16 spell_id); + void ClearItemReuseTimer(); + void ClearItemReuseTimer(uint32 item_id); + void ClearSpellRecastTimer(); + void ClearSpellRecastTimer(uint16 spell_id); + uint32 GetDisciplineReuseTimer(); + uint32 GetDisciplineReuseTimer(uint16 spell_id); + uint32 GetItemReuseTimer(); + uint32 GetItemReuseTimer(uint32 item_id); + uint32 GetSpellRecastTimer(); + uint32 GetSpellRecastTimer(uint16 spell_id); + void SetDisciplineReuseTimer(uint16 spell_id); + void SetDisciplineReuseTimer(uint16 spell_id, uint32 reuse_timer); + void SetItemReuseTimer(uint32 item_id); + void SetItemReuseTimer(uint32 item_id, uint32 reuse_timer); + void SetSpellRecastTimer(uint16 spell_id); + void SetSpellRecastTimer(uint16 spell_id, uint32 reuse_timer); + int CountAugmentEquippedByID(uint32 item_id); int CountItemEquippedByID(uint32 item_id); bool HasAugmentEquippedByID(uint32 item_id); diff --git a/zone/perl_bot.cpp b/zone/perl_bot.cpp index 84b7d36eb..26568d06b 100644 --- a/zone/perl_bot.cpp +++ b/zone/perl_bot.cpp @@ -255,11 +255,101 @@ void Perl_Bot_Stand(Bot* self) // @categories Script Utility self->Stand(); } +uint32 Perl_Bot_GetSpellRecastTimer(Bot* self) +{ + return self->GetSpellRecastRemainingTime(); +} + +uint32 Perl_Bot_GetSpellRecastTimer(Bot* self, uint16 spell_id) +{ + return self->GetSpellRecastRemainingTime(spell_id); +} + +void Perl_Bot_ClearSpellRecastTimer(Bot* self) +{ + return self->ClearSpellRecastTimer(); +} + +void Perl_Bot_ClearSpellRecastTimer(Bot* self, uint16 spell_id) +{ + return self->ClearSpellRecastTimer(spell_id); +} + +uint32 Perl_Bot_GetDisciplineReuseTimer(Bot* self) +{ + return self->GetDisciplineReuseRemainingTime(); +} + +uint32 Perl_Bot_GetDisciplineReuseTimer(Bot* self, uint16 spell_id) +{ + return self->GetDisciplineReuseRemainingTime(spell_id); +} + +void Perl_Bot_ClearDisciplineReuseTimer(Bot* self) +{ + return self->ClearDisciplineReuseTimer(); +} + +void Perl_Bot_ClearDisciplineReuseTimer(Bot* self, uint16 spell_id) +{ + return self->ClearDisciplineReuseTimer(spell_id); +} + +void Perl_Bot_SetDisciplineReuseTimer(Bot* self, uint16 spell_id) +{ + return self->SetDisciplineReuseTimer(spell_id); +} + +void Perl_Bot_SetDisciplineReuseTimer(Bot* self, uint16 spell_id, uint32 recast_delay) +{ + return self->SetDisciplineReuseTimer(spell_id); +} + +void Perl_Bot_SetItemReuseTimer(Bot* self, uint32 item_id) +{ + return self->SetItemReuseTimer(item_id); +} + +void Perl_Bot_SetItemReuseTimer(Bot* self, uint32 item_id, uint32 reuse_timer) +{ + return self->SetItemReuseTimer(item_id, reuse_timer); +} + +void Perl_Bot_SetSpellRecastTimer(Bot* self, uint16 spell_id) +{ + return self->SetSpellRecastTimer(spell_id); +} + +void Perl_Bot_SetSpellRecastTimer(Bot* self, uint16 spell_id, uint32 recast_delay) +{ + return self->SetSpellRecastTimer(spell_id, recast_delay); +} + int Perl_Bot_GetItemIDAt(Bot* self, int16 slot_id) // @categories Inventory and Items { return self->GetItemIDAt(slot_id); } +uint32 Perl_Bot_GetItemReuseTimer(Bot* self) +{ + return self->GetItemReuseRemainingTime(); +} + +uint32 Perl_Bot_GetItemReuseTimer(Bot* self, uint32 item_id) +{ + return self->GetItemReuseRemainingTime(item_id); +} + +void Perl_Bot_ClearItemReuseTimer(Bot* self) +{ + return self->ClearItemReuseTimer(); +} + +void Perl_Bot_ClearItemReuseTimer(Bot* self, uint32 item_id) +{ + return self->ClearItemReuseTimer(item_id); +} + int Perl_Bot_GetAugmentIDAt(Bot* self, int16 slot_id, uint8 aug_slot) // @categories Inventory and Items { return self->GetAugmentIDAt(slot_id, aug_slot); @@ -555,7 +645,13 @@ void perl_register_bot() package.add("ApplySpellRaid", (void(*)(Bot*, int, int, int, bool))&Perl_Bot_ApplySpellRaid); package.add("ApplySpellRaid", (void(*)(Bot*, int, int, int, bool, bool))&Perl_Bot_ApplySpellRaid); package.add("Camp", (void(*)(Bot*))&Perl_Bot_Camp); - package.add("Camp", (void(*)(Bot*, bool))&Perl_Bot_Camp); + package.add("Camp", (void(*)(Bot*, bool))&Perl_Bot_Camp); + package.add("ClearDisciplineReuseTimer", (void(*)(Bot*))&Perl_Bot_ClearDisciplineReuseTimer); + package.add("ClearDisciplineReuseTimer", (void(*)(Bot*, uint16))&Perl_Bot_ClearDisciplineReuseTimer); + package.add("ClearItemReuseTimer", (void(*)(Bot*))&Perl_Bot_ClearItemReuseTimer); + package.add("ClearItemReuseTimer", (void(*)(Bot*, uint32))&Perl_Bot_ClearItemReuseTimer); + package.add("ClearSpellRecastTimer", (void(*)(Bot*))&Perl_Bot_ClearSpellRecastTimer); + package.add("ClearSpellRecastTimer", (void(*)(Bot*, uint16))&Perl_Bot_ClearSpellRecastTimer); package.add("CountAugmentEquippedByID", &Perl_Bot_CountAugmentEquippedByID); package.add("CountBotItem", &Perl_Bot_CountBotItem); package.add("CountItemEquippedByID", &Perl_Bot_CountItemEquippedByID); @@ -594,6 +690,13 @@ void perl_register_bot() package.add("HasBotItem", &Perl_Bot_HasBotItem); package.add("HasBotSpellEntry", &Perl_Bot_HasBotSpellEntry); package.add("HasItemEquippedByID", &Perl_Bot_HasItemEquippedByID); + package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetDisciplineReuseTimer); + package.add("GetDisciplineReuseTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetDisciplineReuseTimer); + package.add("GetItemEquippedByID", &Perl_Bot_HasItemEquippedByID); + package.add("GetItemReuseTimer", (uint32(*)(Bot*))&Perl_Bot_GetItemReuseTimer); + package.add("GetItemReuseTimer", (uint32(*)(Bot*, uint32))&Perl_Bot_GetItemReuseTimer); + package.add("GetSpellRecastTimer", (uint32(*)(Bot*))&Perl_Bot_GetSpellRecastTimer); + package.add("GetSpellRecastTimer", (uint32(*)(Bot*, uint16))&Perl_Bot_GetSpellRecastTimer); package.add("IsGrouped", &Perl_Bot_IsGrouped); package.add("IsSitting", &Perl_Bot_IsSitting); package.add("IsStanding", &Perl_Bot_IsStanding); @@ -608,6 +711,10 @@ void perl_register_bot() package.add("SendSpellAnim", &Perl_Bot_SendSpellAnim); package.add("SetExpansionBitmask", (void(*)(Bot*, int))&Perl_Bot_SetExpansionBitmask); package.add("SetExpansionBitmask", (void(*)(Bot*, int, bool))&Perl_Bot_SetExpansionBitmask); + package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16))&Perl_Bot_SetDisciplineReuseTimer); + package.add("SetDisciplineReuseTimer", (void(*)(Bot*, uint16, uint32))&Perl_Bot_SetDisciplineReuseTimer); + package.add("SetItemReuseTimer", (void(*)(Bot*, uint32))&Perl_Bot_SetItemReuseTimer); + package.add("SetItemReuseTimer", (void(*)(Bot*, uint32, uint32))&Perl_Bot_SetItemReuseTimer); package.add("SetSpellDuration", (void(*)(Bot*, int))&Perl_Bot_SetSpellDuration); package.add("SetSpellDuration", (void(*)(Bot*, int, int))&Perl_Bot_SetSpellDuration); package.add("SetSpellDuration", (void(*)(Bot*, int, int, int))&Perl_Bot_SetSpellDuration); @@ -621,6 +728,8 @@ void perl_register_bot() package.add("SetSpellDurationRaid", (void(*)(Bot*, int, int, int))&Perl_Bot_SetSpellDurationRaid); package.add("SetSpellDurationRaid", (void(*)(Bot*, int, int, int, bool))&Perl_Bot_SetSpellDurationRaid); package.add("SetSpellDurationRaid", (void(*)(Bot*, int, int, int, bool, bool))&Perl_Bot_SetSpellDurationRaid); + package.add("SetSpellRecastTimer", (void(*)(Bot*, uint16))&Perl_Bot_SetSpellRecastTimer); + package.add("SetSpellRecastTimer", (void(*)(Bot*, uint16, uint32))&Perl_Bot_SetSpellRecastTimer); package.add("Signal", &Perl_Bot_Signal); package.add("Sit", &Perl_Bot_Sit); package.add("Stand", &Perl_Bot_Stand); diff --git a/zone/spells.cpp b/zone/spells.cpp index 02da6f7a3..4b42683b3 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -402,7 +402,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } } //must use SPA 415 with focus (SPA 127/500/501) to reduce item recast - else if (cast_time && IsClient() && slot == CastingSlot::Item && item_slot != 0xFFFFFFFF) { + else if (cast_time && IsOfClientBot() && slot == CastingSlot::Item && item_slot != 0xFFFFFFFF) { orgcasttime = cast_time; if (cast_time) { cast_time = GetActSpellCasttime(spell_id, cast_time); @@ -1643,6 +1643,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo { DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, inventory_slot); } + if (IsBot() && slot == CastingSlot::Item && inventory_slot != 0xFFFFFFFF) // 10 is an item + { + DeleteChargeFromSlot = GetItemSlotToConsumeCharge(spell_id, inventory_slot); + } // we're done casting, now try to apply the spell if(!SpellFinished(spell_id, spell_target, slot, mana_used, inventory_slot, resist_adjust, false,-1, 0xFFFFFFFF, 0, true)) { @@ -1661,9 +1665,23 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo TryTriggerOnCastFocusEffect(focusTriggerOnCast, spell_id); - if (DeleteChargeFromSlot >= 0) { + if (IsClient() && DeleteChargeFromSlot >= 0) { CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true); } + else if (IsBot() && DeleteChargeFromSlot >= 0) { + EQ::ItemInstance* inst = CastToBot()->GetBotItem(DeleteChargeFromSlot); + if (inst) { + inst->SetCharges((inst->GetCharges() - 1)); + if (!database.botdb.SaveItemBySlot(CastToBot(), DeleteChargeFromSlot, inst)) { + GetOwner()->Message(Chat::Red, "%s says, 'Failed to save item [%i] slot [%i] for [%s].", inst->GetID(), DeleteChargeFromSlot, GetCleanName()); + return; + } + } + else { + GetOwner()->Message(Chat::Red, "%s says, 'Failed to update item charges.", GetCleanName()); + LogError("Failed to update item charges for {}.", GetCleanName()); + } + } // // at this point the spell has successfully been cast @@ -2739,6 +2757,18 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)){ CastToClient()->SetItemRecastTimer(spell_id, inventory_slot); } + else if (IsBot() && CastToBot()->GetIsUsingItemClick() && slot == CastingSlot::Item) { + EQ::ItemInstance* inst = CastToBot()->GetBotItem(inventory_slot); + const EQ::ItemData* item = nullptr; + if (inst && inst->GetItem()) { + item = inst->GetItem(); + CastToBot()->SetItemReuseTimer(item->ID); + CastToBot()->SetIsUsingItemClick(false); + } + else { + GetOwner()->Message(Chat::Red, "%s says, 'Failed to set item reuse timer for %s.", GetCleanName()); + } + } if (IsNPC()) { CastToNPC()->AI_Event_SpellCastFinished(true, static_cast(slot)); @@ -6971,7 +7001,11 @@ void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time } if (cast_time != 0) { - CastSpell(spell_id, target_id, CastingSlot::Item, cast_time, 0, 0, item_slot); + if (!CastSpell(spell_id, target_id, CastingSlot::Item, cast_time, 0, 0, item_slot)) { + if (IsBot()) { + GetOwner()->Message(Chat::Red, "%s says, 'Casting failed for %s. This could be due to zone restrictions, target restrictions or other limiting factors.", GetCleanName(), CastToBot()->GetBotItem(item_slot)->GetItem()->Name); + } + } } //Instant cast items do not stop bard songs or interrupt casting. else if (CheckItemRaceClassDietyRestrictionsOnCast(item_slot) && DoCastingChecksOnCaster(spell_id, CastingSlot::Item)) { @@ -6980,6 +7014,25 @@ void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time if (IsClient() && DeleteChargeFromSlot >= 0) { CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true); } + else if (IsBot() && DeleteChargeFromSlot >= 0) { + EQ::ItemInstance* inst = CastToBot()->GetBotItem(DeleteChargeFromSlot); + if (inst) { + inst->SetCharges((inst->GetCharges() - 1)); + if (!database.botdb.SaveItemBySlot(CastToBot(), DeleteChargeFromSlot, inst)) { + GetOwner()->Message(Chat::Red, "%s says, 'Failed to save item [%i] slot [%i] for [%s].", inst->GetID(), DeleteChargeFromSlot, GetCleanName()); + return; + } + } + else { + GetOwner()->Message(Chat::Red, "%s says, 'Failed to update item charges.", GetCleanName()); + LogError("Failed to update item charges for {}.", GetCleanName()); + } + } + } + else { + if (IsBot()) { + GetOwner()->Message(Chat::Red, "%s says, 'Casting failed for %s. This could be due to zone restrictions, target restrictions or other limiting factors.", GetCleanName(), CastToBot()->GetBotItem(item_slot)->GetItem()->Name); + } } } } @@ -6988,12 +7041,17 @@ int16 Mob::GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot) { int16 DeleteChargeFromSlot = -1; - if (!IsClient() || inventory_slot == 0xFFFFFFFF) { + if (!IsOfClientBot() || inventory_slot == 0xFFFFFFFF) { return DeleteChargeFromSlot; } EQ::ItemInstance *item = nullptr; - item = CastToClient()->GetInv().GetItem(inventory_slot); + if (IsClient()) { + item = CastToClient()->GetInv().GetItem(inventory_slot); + } + else if (IsBot()) { + item = CastToBot()->GetBotItem(inventory_slot); + } bool fromaug = false; EQ::ItemData* augitem = nullptr; @@ -7036,7 +7094,12 @@ int16 Mob::GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot) } else{ LogSpells("Item used to cast spell [{}] was missing from inventory slot [{}] after casting!", spell_id, inventory_slot); - Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot); + if (IsClient()) { + Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i", inventory_slot); + } + else if (IsBot()) { + CastToBot()->GetOwner()->Message(Chat::Red, "Casting Error: Active casting item not found in inventory slot %i for %s", inventory_slot, GetCleanName()); + } InterruptSpell(); return DeleteChargeFromSlot; }