/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "bot_database.h"
#include "common/data_verification.h"
#include "common/eqemu_logsys.h"
#include "common/repositories/bot_blocked_buffs_repository.h"
#include "common/repositories/bot_buffs_repository.h"
#include "common/repositories/bot_create_combinations_repository.h"
#include "common/repositories/bot_data_repository.h"
#include "common/repositories/bot_heal_rotation_members_repository.h"
#include "common/repositories/bot_heal_rotation_targets_repository.h"
#include "common/repositories/bot_heal_rotations_repository.h"
#include "common/repositories/bot_inspect_messages_repository.h"
#include "common/repositories/bot_inventories_repository.h"
#include "common/repositories/bot_owner_options_repository.h"
#include "common/repositories/bot_pet_buffs_repository.h"
#include "common/repositories/bot_pet_inventories_repository.h"
#include "common/repositories/bot_pets_repository.h"
#include "common/repositories/bot_settings_repository.h"
#include "common/repositories/bot_spell_casting_chances_repository.h"
#include "common/repositories/bot_spells_entries_repository.h"
#include "common/repositories/bot_stances_repository.h"
#include "common/repositories/bot_timers_repository.h"
#include "common/repositories/character_data_repository.h"
#include "common/repositories/group_id_repository.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "zone/bot.h"
#include "zone/client.h"
#include "zone/zonedb.h"
#include "fmt/format.h"
bool BotDatabase::LoadBotCommandSettings(std::map>> &bot_command_settings)
{
bot_command_settings.clear();
query = "SELECT `bot_command`, `access`, `aliases` FROM `bot_command_settings`";
auto results = database.QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row) {
bot_command_settings[row[0]].first = Strings::ToInt(row[1]);
if (row[2][0] == 0)
continue;
auto aliases = Strings::Split(row[2], '|');
for (auto iter : aliases) {
if (!iter.empty())
bot_command_settings[row[0]].second.push_back(iter);
}
}
return true;
}
template
inline std::vector join_pair(
const std::string &glue,
const std::pair &encapsulation,
const std::vector> &src
)
{
if (src.empty()) {
return {};
}
std::vector output;
for (const std::pair &src_iter: src) {
output.emplace_back(
fmt::format(
"{}{}{}{}{}{}{}",
encapsulation.first,
src_iter.first,
encapsulation.second,
glue,
encapsulation.first,
src_iter.second,
encapsulation.second
)
);
}
return output;
}
template
inline std::string
ImplodePair(const std::string &glue, const std::pair &encapsulation, const std::vector &src)
{
if (src.empty()) {
return {};
}
std::ostringstream oss;
for (const T &src_iter: src) {
oss << encapsulation.first << src_iter << encapsulation.second << glue;
}
std::string output(oss.str());
output.resize(output.size() - glue.size());
return output;
}
bool BotDatabase::UpdateInjectedBotCommandSettings(const std::vector> &injected)
{
if (injected.size()) {
query = fmt::format(
"REPLACE INTO `bot_command_settings`(`bot_command`, `access`) VALUES {}",
ImplodePair(
",",
std::pair('(', ')'),
join_pair(",", std::pair('\'', '\''), injected)
)
);
if (!database.QueryDatabase(query).Success()) {
return false;
}
LogInfo(
"[{}] New Bot Command{} Added",
injected.size(),
(injected.size() == 1 ? "" : "s")
);
}
return true;
}
bool BotDatabase::UpdateOrphanedBotCommandSettings(const std::vector &orphaned)
{
if (orphaned.size()) {
query = fmt::format(
"DELETE FROM `bot_command_settings` WHERE `bot_command` IN ({})",
ImplodePair(",", std::pair('\'', '\''), orphaned)
);
if (!database.QueryDatabase(query).Success()) {
return false;
}
LogInfo(
"[{}] Orphaned Bot Command{} Deleted",
orphaned.size(),
(orphaned.size() == 1 ? "" : "s")
);
}
return true;
}
bool BotDatabase::LoadBotSpellCastingChances()
{
const auto& l = BotSpellCastingChancesRepository::All(database);
if (l.empty()) {
return false;
}
std::vector conditions = { };
for (const auto& e : l) {
if (
e.spell_type_index >= Bot::SPELL_TYPE_COUNT ||
!IsPlayerClass(e.class_id) ||
e.stance_index >= Stance::AEBurn
) {
continue;
}
const uint8 class_index = (e.class_id - 1);
conditions = {
e.nHSND_value,
e.pH_value,
e.pS_value,
e.pHS_value,
e.pN_value,
e.pHN_value,
e.pSN_value,
e.pHSN_value,
e.pD_value,
e.pHD_value,
e.pSD_value,
e.pHSD_value,
e.pND_value,
e.pHND_value,
e.pSND_value,
e.pHSN_value
};
for (uint8 index = 0; index < conditions.size(); index++) {
uint8 value = conditions[index];
if (!value) {
continue;
}
if (value > 100) {
value = 100;
}
Bot::spell_casting_chances[e.spell_type_index][class_index][e.stance_index][index] = value;
}
}
return true;
}
bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag)
{
if (
bot_name.empty() ||
bot_name.size() > 60 ||
!database.CheckNameFilter(bot_name) ||
database.IsNameUsed(bot_name)
) {
return false;
}
available_flag = true;
return true;
}
bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count)
{
if (!owner_id) {
return false;
}
bot_count = BotDataRepository::Count(
database,
fmt::format(
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
owner_id
)
);
if (IsPlayerClass(class_id)) {
bot_class_count = BotDataRepository::Count(
database,
fmt::format(
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'",
owner_id,
class_id
)
);
}
return true;
}
bool BotDatabase::LoadBotsList(const uint32 owner_id, std::list& bots_list, bool by_account)
{
if (!owner_id) {
return false;
}
BotsAvailableList ble;
if (by_account) {
const std::string& owner_name = database.GetCharNameByID(owner_id);
const auto& l = BotDataRepository::GetWhere(
database,
fmt::format(
SQL(
`owner_id` IN
(
SELECT `id` FROM `character_data` WHERE `account_id` =
(
SELECT `account_id` FROM `character_data` WHERE `id` = {}
)
)
AND
`name` NOT LIKE '%-deleted-%'
),
owner_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
ble.bot_id = e.bot_id;
ble.class_ = e.class_;
ble.level = e.level;
ble.race = e.race;
ble.gender = e.gender;
ble.owner_id = e.owner_id;
strn0cpy(ble.bot_name, e.name.c_str(), sizeof(ble.bot_name));
strn0cpy(ble.owner_name, owner_name.c_str(), sizeof(ble.owner_name));
bots_list.emplace_back(ble);
}
} else {
const auto& l = BotDataRepository::GetWhere(
database,
fmt::format(
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
owner_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
ble.bot_id = e.bot_id;
ble.class_ = e.class_;
ble.level = e.level;
ble.race = e.race;
ble.gender = e.gender;
ble.owner_id = e.owner_id;
strn0cpy(ble.bot_name, e.name.c_str(), sizeof(ble.bot_name));
strn0cpy(ble.owner_name, "You", sizeof(ble.owner_name));
bots_list.emplace_back(ble);
}
}
return true;
}
uint32 BotDatabase::GetOwnerID(const uint32 bot_id)
{
if (!bot_id) {
return 0;
}
const auto& l = BotDataRepository::FindOne(database, bot_id);
return l.bot_id ? l.owner_id : 0;
}
bool BotDatabase::LoadBotID(const std::string& bot_name, uint32& bot_id, uint8& bot_class_id)
{
if (bot_name.empty()) {
return false;
}
const auto& l = BotDataRepository::GetWhere(
database,
fmt::format(
"`name` = '{}' AND `name` NOT LIKE '%-deleted-%' LIMIT 1",
Strings::Escape(bot_name)
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
bot_id = e.bot_id;
bot_class_id = e.class_;
return true;
}
bool BotDatabase::LoadBot(const uint32 bot_id, Bot*& loaded_bot)
{
if (!bot_id || loaded_bot) {
return false;
}
const auto& e = BotDataRepository::FindOne(database, bot_id);
if (!e.bot_id) {
return false;
}
auto d = Bot::CreateDefaultNPCTypeStructForBot(
e.name,
e.last_name,
e.level,
e.race,
e.class_,
e.gender
);
auto t = Bot::FillNPCTypeStruct(
e.spells_id,
e.name,
e.last_name,
e.level,
e.race,
e.class_,
e.gender,
e.size,
e.face,
e.hair_style,
e.hair_color,
e.eye_color_1,
e.eye_color_2,
e.beard,
e.beard_color,
e.drakkin_heritage,
e.drakkin_tattoo,
e.drakkin_details,
e.hp,
e.mana,
d->MR,
d->CR,
d->DR,
d->FR,
d->PR,
d->Corrup,
d->AC,
d->STR,
d->STA,
d->DEX,
d->AGI,
d->INT,
d->WIS,
d->CHA,
d->ATK
);
safe_delete(d);
loaded_bot = new Bot(
bot_id,
e.owner_id,
e.spells_id,
e.time_spawned,
e.zone_id,
t
);
if (loaded_bot) {
loaded_bot->SetSurname(e.last_name);
loaded_bot->SetTitle(e.title);
loaded_bot->SetSuffix(e.suffix);
loaded_bot->SetExpansionBitmask(e.expansion_bitmask);
}
return true;
}
bool BotDatabase::SaveNewBot(Bot* b, uint32& bot_id)
{
if (!b) {
return false;
}
auto e = BotDataRepository::NewEntity();
e.owner_id = b->GetBotOwnerCharacterID();
e.spells_id = b->GetBotSpellID();
e.name = b->GetCleanName();
e.last_name = b->GetLastName();
e.title = b->GetTitle();
e.suffix = b->GetSuffix();
e.zone_id = b->GetLastZoneID();
e.gender = b->GetGender();
e.race = b->GetBaseRace();
e.class_ = b->GetClass();
e.level = b->GetLevel();
e.creation_day = std::time(nullptr);
e.last_spawn = std::time(nullptr);
e.size = b->GetSize();
e.face = b->GetLuclinFace();
e.hair_color = b->GetHairColor();
e.hair_style = b->GetHairStyle();
e.beard = b->GetBeard();
e.beard_color = b->GetBeardColor();
e.eye_color_1 = b->GetEyeColor1();
e.eye_color_2 = b->GetEyeColor2();
e.drakkin_heritage = b->GetDrakkinHeritage();
e.drakkin_tattoo = b->GetDrakkinTattoo();
e.drakkin_details = b->GetDrakkinDetails();
e.ac = b->GetBaseAC();
e.atk = b->GetBaseATK();
e.hp = b->GetHP();
e.mana = b->GetMana();
e.str = b->GetBaseSTR();
e.sta = b->GetBaseSTA();
e.cha = b->GetBaseCHA();
e.dex = b->GetBaseDEX();
e.int_ = b->GetBaseINT();
e.agi = b->GetBaseAGI();
e.wis = b->GetBaseWIS();
e.fire = b->GetBaseFR();
e.cold = b->GetBaseCR();
e.magic = b->GetBaseMR();
e.poison = b->GetBasePR();
e.disease = b->GetBaseDR();
e.corruption = b->GetBaseCorrup();
e.expansion_bitmask = b->GetExpansionBitmask();
e = BotDataRepository::InsertOne(database, e);
if (!e.bot_id) {
return false;
}
bot_id = e.bot_id;
return true;
}
bool BotDatabase::SaveBot(Bot* b)
{
if (!b) {
return false;
}
auto e = BotDataRepository::FindOne(database, b->GetBotID());
if (!e.bot_id) {
return false;
}
e.owner_id = b->GetBotOwnerCharacterID();
e.spells_id = b->GetBotSpellID();
e.name = b->GetCleanName();
e.last_name = b->GetLastName();
e.title = b->GetTitle();
e.suffix = b->GetSuffix();
e.zone_id = b->GetLastZoneID();
e.gender = b->GetBaseGender();
e.race = b->GetBaseRace();
e.class_ = b->GetClass();
e.level = b->GetLevel();
e.last_spawn = std::time(nullptr);
e.time_spawned = b->GetTotalPlayTime();
e.size = b->GetSize();
e.face = b->GetLuclinFace();
e.hair_color = b->GetHairColor();
e.hair_style = b->GetHairStyle();
e.beard = b->GetBeard();
e.beard_color = b->GetBeardColor();
e.eye_color_1 = b->GetEyeColor1();
e.eye_color_2 = b->GetEyeColor2();
e.drakkin_heritage = b->GetDrakkinHeritage();
e.drakkin_tattoo = b->GetDrakkinTattoo();
e.drakkin_details = b->GetDrakkinDetails();
e.ac = b->GetBaseAC();
e.atk = b->GetBaseATK();
e.hp = b->GetHP();
e.mana = b->GetMana();
e.str = b->GetBaseSTR();
e.sta = b->GetBaseSTA();
e.cha = b->GetBaseCHA();
e.dex = b->GetBaseDEX();
e.int_ = b->GetBaseINT();
e.agi = b->GetBaseAGI();
e.wis = b->GetBaseWIS();
e.fire = b->GetBaseFR();
e.cold = b->GetBaseCR();
e.magic = b->GetBaseMR();
e.poison = b->GetBasePR();
e.disease = b->GetBaseDR();
e.corruption = b->GetBaseCorrup();
e.expansion_bitmask = b->GetExpansionBitmask();
return BotDataRepository::UpdateOne(database, e);
}
bool BotDatabase::DeleteBot(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotDataRepository::DeleteOne(database, bot_id);
return true;
}
bool BotDatabase::LoadBuffs(Bot* b)
{
if (!b) {
return false;
}
const auto& l = BotBuffsRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {}",
b->GetBotID()
)
);
if (l.empty()) {
return true;
}
auto buffs = b->GetBuffs();
if (!buffs) {
return false;
}
uint32 max_slots = b->GetMaxBuffSlots();
for (int index = 0; index < max_slots; index++) {
buffs[index].spellid = SPELL_UNKNOWN;
}
uint32 buff_count = 0;
for (const auto& e : l) {
if (buff_count >= BUFF_COUNT) {
continue;
}
buffs[buff_count].spellid = e.spell_id;
buffs[buff_count].casterlevel = e.caster_level;
buffs[buff_count].ticsremaining = e.tics_remaining;
buffs[buff_count].counters = 0;
if (CalculatePoisonCounters(buffs[buff_count].spellid) > 0) {
buffs[buff_count].counters = e.poison_counters;
} else if (CalculateDiseaseCounters(buffs[buff_count].spellid) > 0) {
buffs[buff_count].counters = e.disease_counters;
} else if (CalculateCurseCounters(buffs[buff_count].spellid) > 0) {
buffs[buff_count].counters = e.curse_counters;
} else if (CalculateCorruptionCounters(buffs[buff_count].spellid) > 0) {
buffs[buff_count].counters = e.corruption_counters;
}
buffs[buff_count].hit_number = e.numhits;
buffs[buff_count].melee_rune = e.melee_rune;
buffs[buff_count].magic_rune = e.magic_rune;
buffs[buff_count].dot_rune = e.dot_rune;
buffs[buff_count].persistant_buff = e.persistent;
buffs[buff_count].caston_x = e.caston_x;
buffs[buff_count].caston_y = e.caston_y;
buffs[buff_count].caston_z = e.caston_z;
buffs[buff_count].ExtraDIChance = e.extra_di_chance;
buffs[buff_count].instrument_mod = e.instrument_mod;
buffs[buff_count].casterid = 0;
++buff_count;
}
return true;
}
bool BotDatabase::SaveBuffs(Bot* b)
{
if (!b) {
return false;
}
if (!DeleteBuffs(b->GetBotID())) {
return false;
}
auto buffs = b->GetBuffs();
if (!buffs) {
return false;
}
auto e = BotBuffsRepository::NewEntity();
e.bot_id = b->GetBotID();
std::vector v = { };
for (int buff_index = 0; buff_index < BUFF_COUNT; ++buff_index) {
if (!IsValidSpell(buffs[buff_index].spellid)) {
continue;
}
e.spell_id = buffs[buff_index].spellid;
e.caster_level = buffs[buff_index].casterlevel;
e.duration_formula = spells[buffs[buff_index].spellid].buff_duration_formula;
e.tics_remaining = buffs[buff_index].ticsremaining;
e.poison_counters = CalculatePoisonCounters(buffs[buff_index].spellid) > 0 ? buffs[buff_index].counters : 0;
e.disease_counters = CalculateDiseaseCounters(buffs[buff_index].spellid) > 0 ? buffs[buff_index].counters : 0;
e.curse_counters = CalculateCurseCounters(buffs[buff_index].spellid) > 0 ? buffs[buff_index].counters : 0;
e.corruption_counters = CalculateCorruptionCounters(buffs[buff_index].spellid) > 0 ? buffs[buff_index].counters : 0;
e.numhits = buffs[buff_index].hit_number;
e.melee_rune = buffs[buff_index].melee_rune;
e.magic_rune = buffs[buff_index].magic_rune;
e.dot_rune = buffs[buff_index].dot_rune;
e.persistent = buffs[buff_index].persistant_buff ? 1 : 0;
e.caston_x = buffs[buff_index].caston_x;
e.caston_y = buffs[buff_index].caston_y;
e.caston_z = buffs[buff_index].caston_z;
e.extra_di_chance = buffs[buff_index].ExtraDIChance;
v.emplace_back(e);
}
if (!v.empty()) {
const int inserted = BotBuffsRepository::InsertMany(database, v);
if (!inserted) {
DeleteBuffs(b->GetBotID());
return false;
}
}
return true;
}
bool BotDatabase::DeleteBuffs(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotBuffsRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
return true;
}
bool BotDatabase::LoadStance(const uint32 bot_id, int& bot_stance)
{
if (!bot_id) {
return false;
}
const auto& l = BotStancesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} LIMIT 1",
bot_id
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
bot_stance = e.stance_id;
return true;
}
bool BotDatabase::LoadStance(Bot* b, bool& stance_flag)
{
if (!b) {
return false;
}
b->SetDefaultBotStance();
const auto& l = BotStancesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} LIMIT 1",
b->GetBotID()
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
b->SetBotStance(e.stance_id);
stance_flag = true;
return true;
}
bool BotDatabase::SaveStance(const uint32 bot_id, const int bot_stance)
{
if (!bot_id) {
return false;
}
return BotStancesRepository::ReplaceOne(
database,
BotStancesRepository::BotStances{
.bot_id = bot_id,
.stance_id = static_cast(bot_stance)
}
);
}
bool BotDatabase::SaveStance(Bot* b)
{
if (!b) {
return false;
}
return BotStancesRepository::ReplaceOne(
database,
BotStancesRepository::BotStances{
.bot_id = b->GetBotID(),
.stance_id = b->GetBotStance()
}
);
}
bool BotDatabase::DeleteStance(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotStancesRepository::DeleteOne(database, bot_id);
return true;
}
bool BotDatabase::LoadTimers(Bot* b)
{
if (!b) {
return false;
}
const auto& l = BotTimersRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {}",
b->GetBotID()
)
);
std::vector v;
BotTimer t{ };
for (const auto& e : l) {
if (e.timer_value < (Timer::GetCurrentTime() + e.recast_time)) {
t.timer_id = e.timer_id;
t.timer_value = e.timer_value;
t.recast_time = e.recast_time;
t.is_spell = e.is_spell;
t.is_disc = e.is_disc;
t.spell_id = e.spell_id;
t.is_item = e.is_item;
t.item_id = e.item_id;
v.push_back(t);
}
}
if (!v.empty()) {
b->SetBotTimers(v);
}
return true;
}
bool BotDatabase::SaveTimers(Bot* b)
{
if (!b) {
return false;
}
if (!DeleteTimers(b->GetBotID())) {
return false;
}
std::vector v = b->GetBotTimers();
if (v.empty()) {
return true;
}
std::vector l;
if (!v.empty()) {
for (auto& bot_timer : v) {
if (bot_timer.timer_value <= Timer::GetCurrentTime()) {
continue;
}
auto e = BotTimersRepository::BotTimers{
.bot_id = b->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,
.is_disc = bot_timer.is_disc,
.spell_id = bot_timer.spell_id,
.is_item = bot_timer.is_item,
.item_id = bot_timer.item_id
};
l.push_back(e);
}
if (l.empty()) {
return true;
}
BotTimersRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
b->GetBotID()
)
);
const int inserted = BotTimersRepository::InsertMany(database, l);
if (!inserted) {
DeleteTimers(b->GetBotID());
return false;
}
}
return true;
}
bool BotDatabase::DeleteTimers(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotTimersRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
return true;
}
bool BotDatabase::QueryInventoryCount(const uint32 bot_id, uint32& item_count)
{
if (!bot_id) {
return false;
}
item_count = BotInventoriesRepository::Count(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
return true;
}
bool BotDatabase::LoadItems(const uint32 bot_id, EQ::InventoryProfile& inventory_inst)
{
if (!bot_id) {
return false;
}
const auto& l = BotInventoriesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} ORDER BY `slot_id`",
bot_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
if (!EQ::ValueWithin(e.slot_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) {
continue;
}
auto inst = database.CreateItem(
e.item_id,
e.inst_charges,
e.augment_1,
e.augment_2,
e.augment_3,
e.augment_4,
e.augment_5,
e.augment_6
);
if (!inst) {
LogError(
"Warning: bot_id [{}] has an invalid item_id [{}] in slot_id [{}]",
bot_id,
e.item_id,
e.slot_id
);
continue;
}
if (e.inst_charges == INT16_MAX) {
inst->SetCharges(-1);
} else if (
e.inst_charges == 0 &&
inst->IsStackable()
) { // Stackable items need a minimum charge of 1 remain moveable.
inst->SetCharges(1);
} else {
inst->SetCharges(e.inst_charges);
}
if (e.inst_color) {
inst->SetColor(e.inst_color);
}
if (inst->GetItem()->Attuneable) {
if (e.inst_no_drop) {
inst->SetAttuned(true);
} else if (EQ::ValueWithin(e.slot_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) {
inst->SetAttuned(true);
}
}
if (!e.inst_custom_data.empty()) {
inst->SetCustomDataString(e.inst_custom_data);
}
inst->SetOrnamentIcon(e.ornament_icon);
inst->SetOrnamentationIDFile(e.ornament_id_file);
inst->SetOrnamentHeroModel(e.ornament_hero_model);
if (inventory_inst.PutItem(e.slot_id, *inst) == INVALID_INDEX) {
LogError(
"Warning: Invalid slot_id for item in inventory: bot_id [{}] item_id [{}] slot_id [{}]",
bot_id,
e.item_id,
e.slot_id
);
}
safe_delete(inst);
}
return true;
}
bool BotDatabase::DeleteItems(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotInventoriesRepository::DeleteOne(database, bot_id);
return true;
}
bool BotDatabase::LoadItemBySlot(const uint32 bot_id, const uint32 slot_id, uint32& item_id)
{
if (!bot_id || slot_id > EQ::invslot::EQUIPMENT_END) {
return false;
}
const auto& l = BotInventoriesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} AND `slot_id` = {} LIMIT 1",
bot_id,
slot_id
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
item_id = e.item_id;
return true;
}
bool BotDatabase::LoadItemSlots(const uint32 bot_id, std::map& m)
{
if (!bot_id) {
return false;
}
const auto& l = BotInventoriesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
m.emplace(std::pair(e.slot_id, e.item_id));
}
return true;
}
bool BotDatabase::SaveItemBySlot(Bot* b, const uint32 slot_id, const EQ::ItemInstance* inst)
{
if (
!b ||
!b->GetBotID() ||
slot_id > EQ::invslot::EQUIPMENT_END
) {
return false;
}
if (!inst || !inst->GetID()) {
return true;
}
DeleteItemBySlot(b->GetBotID(), slot_id);
uint32 augment_id[EQ::invaug::SOCKET_COUNT] = { 0, 0, 0, 0, 0, 0 };
for (uint16 slot_id = EQ::invaug::SOCKET_BEGIN; slot_id <= EQ::invaug::SOCKET_END; ++slot_id) {
augment_id[slot_id] = inst->GetAugmentItemID(slot_id);
}
uint16 item_charges = 0;
if (inst->GetCharges() >= 0) {
item_charges = inst->GetCharges();
} else {
item_charges = INT16_MAX;
}
auto e = BotInventoriesRepository::NewEntity();
e.bot_id = b->GetBotID();
e.slot_id = slot_id;
e.item_id = inst->GetID();
e.inst_charges = item_charges;
e.inst_color = inst->GetColor();
e.inst_no_drop = inst->IsAttuned() ? 1 : 0;
e.inst_custom_data = inst->GetCustomDataString();
e.ornament_icon = inst->GetOrnamentationIcon();
e.ornament_id_file = inst->GetOrnamentationIDFile();
e.ornament_hero_model = inst->GetOrnamentHeroModel();
e.augment_1 = augment_id[0];
e.augment_2 = augment_id[1];
e.augment_3 = augment_id[2];
e.augment_4 = augment_id[3];
e.augment_5 = augment_id[4];
e.augment_6 = augment_id[5];
return BotInventoriesRepository::InsertOne(database, e).inventories_index;
}
bool BotDatabase::DeleteItemBySlot(const uint32 bot_id, const uint32 slot_id)
{
if (!bot_id || slot_id > EQ::invslot::EQUIPMENT_END) {
return false;
}
BotInventoriesRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {} AND `slot_id` = {}",
bot_id,
slot_id
)
);
return true;
}
bool BotDatabase::SaveEquipmentColor(const uint32 bot_id, const int16 slot_id, const uint32 color)
{
if (!bot_id) {
return false;
}
const bool all_flag = (slot_id == -2);
if (!EQ::ValueWithin(slot_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END) && !all_flag) {
return false;
}
std::string where_clause;
if (all_flag) {
where_clause = fmt::format(
"IN ({}, {}, {}, {}, {}, {}, {})",
EQ::invslot::slotHead,
EQ::invslot::slotArms,
EQ::invslot::slotWrist1,
EQ::invslot::slotHands,
EQ::invslot::slotChest,
EQ::invslot::slotLegs,
EQ::invslot::slotFeet
);
} else {
where_clause = fmt::format(
"= {}",
slot_id
);
}
return BotInventoriesRepository::UpdateItemColors(database, bot_id, color, where_clause);
}
bool BotDatabase::LoadPetIndex(const uint32 bot_id, uint32& pet_index)
{
if (!bot_id) {
return false;
}
const auto& l = BotPetsRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} LIMIT 1",
bot_id
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
pet_index = e.pets_index;
return true;
}
bool BotDatabase::LoadPetSpellID(const uint32 bot_id, uint32& pet_spell_id)
{
if (!bot_id) {
return false;
}
const auto& l = BotPetsRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} LIMIT 1",
bot_id
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
pet_spell_id = e.spell_id;
return true;
}
bool BotDatabase::LoadPetStats(const uint32 bot_id, std::string& pet_name, uint32& pet_mana, uint32& pet_hp, uint32& pet_spell_id)
{
if (!bot_id) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
const auto& l = BotPetsRepository::GetWhere(
database,
fmt::format(
"`pets_index` = {} LIMIT 1",
saved_pet_index
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
pet_spell_id = e.spell_id;
pet_name = e.name;
pet_mana = e.mana;
pet_hp = e.hp;
return true;
}
bool BotDatabase::SavePetStats(const uint32 bot_id, const std::string& pet_name, const uint32 pet_mana, const uint32 pet_hp, const uint32 pet_spell_id)
{
if (!bot_id || pet_name.empty() || !pet_spell_id || pet_spell_id > SPDAT_RECORDS) {
return false;
}
if (
!DeletePetItems(bot_id) ||
!DeletePetBuffs(bot_id) ||
!DeletePetStats(bot_id)
) {
return false;
}
return BotPetsRepository::InsertOne(
database,
BotPetsRepository::BotPets{
.spell_id = pet_spell_id,
.bot_id = bot_id,
.name = pet_name,
.mana = static_cast(pet_mana),
.hp = static_cast(pet_hp)
}
).pets_index;
}
bool BotDatabase::DeletePetStats(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
return BotPetsRepository::DeleteOne(database, saved_pet_index) == 1;
}
bool BotDatabase::LoadPetBuffs(const uint32 bot_id, SpellBuff_Struct* pet_buffs)
{
if (!bot_id) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
const auto& l = BotPetBuffsRepository::GetWhere(
database,
fmt::format(
"`pets_index` = {}",
saved_pet_index
)
);
if (l.empty()) {
return true;
}
uint16 buff_index = 0;
for (const auto& e : l) {
if (buff_index >= PET_BUFF_COUNT) {
break;
}
pet_buffs[buff_index].spellid = e.spell_id;
pet_buffs[buff_index].level = e.caster_level;
pet_buffs[buff_index].duration = e.duration;
if (CalculatePoisonCounters(pet_buffs[buff_index].spellid) > 0) {
pet_buffs[buff_index].counters = CalculatePoisonCounters(pet_buffs[buff_index].spellid);
} else if (CalculateDiseaseCounters(pet_buffs[buff_index].spellid) > 0) {
pet_buffs[buff_index].counters = CalculateDiseaseCounters(pet_buffs[buff_index].spellid);
} else if (CalculateCurseCounters(pet_buffs[buff_index].spellid) > 0) {
pet_buffs[buff_index].counters = CalculateCurseCounters(pet_buffs[buff_index].spellid);
} else if (CalculateCorruptionCounters(pet_buffs[buff_index].spellid) > 0) {
pet_buffs[buff_index].counters = CalculateCorruptionCounters(pet_buffs[buff_index].spellid);
}
++buff_index;
}
return true;
}
bool BotDatabase::SavePetBuffs(const uint32 bot_id, const SpellBuff_Struct* pet_buffs, bool delete_flag)
{
if (
!bot_id ||
!pet_buffs ||
(delete_flag && !DeletePetBuffs(bot_id))
) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
auto e = BotPetBuffsRepository::NewEntity();
e.pets_index = saved_pet_index;
std::vector v;
for (uint16 buff_index = 0; buff_index < PET_BUFF_COUNT; ++buff_index) {
if (!IsValidSpell(pet_buffs[buff_index].spellid)) {
continue;
}
e.spell_id = pet_buffs[buff_index].spellid;
e.caster_level = pet_buffs[buff_index].level;
e.duration = pet_buffs[buff_index].duration;
v.emplace_back(e);
}
if (!v.empty()) {
BotPetBuffsRepository::InsertMany(database, v);
}
return true;
}
bool BotDatabase::DeletePetBuffs(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
BotPetBuffsRepository::DeleteWhere(database, fmt::format("pets_index = {}", saved_pet_index));
return true;
}
bool BotDatabase::LoadPetItems(const uint32 bot_id, uint32* pet_items)
{
if (!bot_id || !pet_items) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
const auto& l = BotPetInventoriesRepository::GetWhere(
database,
fmt::format(
"`pets_index` = {}",
saved_pet_index
)
);
if (l.empty()) {
return true;
}
int16 slot_id = EQ::invslot::EQUIPMENT_BEGIN;
for (const auto& e : l) {
if (!EQ::ValueWithin(slot_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) {
break;
}
pet_items[slot_id] = e.item_id;
++slot_id;
}
return true;
}
bool BotDatabase::SavePetItems(const uint32 bot_id, const uint32* pet_items, bool delete_flag)
{
// Only use 'delete_flag' if not invoked after a botdb.SavePetStats() call
if (
!bot_id ||
!pet_items ||
(delete_flag && !DeletePetItems(bot_id))
) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
auto e = BotPetInventoriesRepository::NewEntity();
e.pets_index = saved_pet_index;
std::vector v;
for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
if (!pet_items[slot_id]) {
continue;
}
e.item_id = pet_items[slot_id];
v.emplace_back(e);
}
if (!v.empty()) {
BotPetInventoriesRepository::InsertMany(database, v);
}
return true;
}
bool BotDatabase::DeletePetItems(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
uint32 saved_pet_index = 0;
if (!LoadPetIndex(bot_id, saved_pet_index)) {
return false;
}
if (!saved_pet_index) {
return true;
}
BotPetInventoriesRepository::DeleteOne(database, saved_pet_index);
return true;
}
bool BotDatabase::LoadInspectMessage(const uint32 bot_id, InspectMessage_Struct& inspect_message)
{
if (!bot_id) {
return false;
}
const auto& e = BotInspectMessagesRepository::FindOne(database, bot_id);
if (!e.bot_id) {
return false;
}
if (e.inspect_message.empty()) {
return true;
}
std::string bot_message = e.inspect_message;
if (bot_message.size() > UINT8_MAX) {
bot_message = bot_message.substr(0, UINT8_MAX);
}
strn0cpy(inspect_message.text, bot_message.c_str(), sizeof(inspect_message.text));
return true;
}
bool BotDatabase::SaveInspectMessage(const uint32 bot_id, const InspectMessage_Struct& inspect_message)
{
if (!bot_id || !DeleteInspectMessage(bot_id)) {
return false;
}
std::string bot_message = inspect_message.text;
if (bot_message.empty()) {
return true;
}
if (bot_message.size() > UINT8_MAX) {
bot_message = bot_message.substr(0, UINT8_MAX);
}
return true;
}
bool BotDatabase::DeleteInspectMessage(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotInspectMessagesRepository::DeleteOne(database, bot_id);
return true;
}
bool BotDatabase::SaveAllInspectMessages(const uint32 owner_id, const InspectMessage_Struct& inspect_message)
{
if (!owner_id || !DeleteAllInspectMessages(owner_id)) {
return false;
}
std::string bot_message = inspect_message.text;
if (bot_message.empty()) {
return true;
}
if (bot_message.size() > UINT8_MAX) {
bot_message = bot_message.substr(0, UINT8_MAX);
}
return BotInspectMessagesRepository::SaveAllInspectMessages(database, owner_id, bot_message);
}
bool BotDatabase::DeleteAllInspectMessages(const uint32 owner_id)
{
if (!owner_id) {
return false;
}
BotInspectMessagesRepository::DeleteAllInspectMessages(database, owner_id);
return true;
}
bool BotDatabase::SaveAllArmorColorBySlot(const uint32 owner_id, const int16 slot_id, const uint32 rgb_value)
{
if (!owner_id) {
return false;
}
BotInventoriesRepository::SaveAllArmorColorsBySlot(database, owner_id, slot_id, rgb_value);
return true;
}
bool BotDatabase::SaveAllArmorColors(const uint32 owner_id, const uint32 rgb_value)
{
if (!owner_id) {
return false;
}
return BotInventoriesRepository::SaveAllArmorColors(database, owner_id, rgb_value);
}
bool BotDatabase::CreateCloneBot(const uint32 bot_id, const std::string& clone_name, uint32& clone_id)
{
if (!bot_id || clone_name.empty()) {
return false;
}
auto e = BotDataRepository::FindOne(database, bot_id);
e.bot_id = 0;
e.name = clone_name;
e = BotDataRepository::InsertOne(database, e);
if (!e.bot_id) {
return false;
}
clone_id = e.bot_id;
return true;
}
bool BotDatabase::CreateCloneBotInventory(const uint32 bot_id, const uint32 clone_id)
{
if (!bot_id || !clone_id) {
return false;
}
auto l = BotInventoriesRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
if (l.empty()) {
return true;
}
for (auto& e : l) {
e.inventories_index = 0;
e.bot_id = clone_id;
}
return BotInventoriesRepository::InsertMany(database, l);
}
bool BotDatabase::LoadOwnerOptions(Client* c)
{
if (!c || !c->CharacterID()) {
return false;
}
const auto& l = BotOwnerOptionsRepository::GetWhere(
database,
fmt::format(
"`owner_id` = {}",
c->CharacterID()
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
c->SetBotOption(static_cast(e.option_type), e.option_value);
}
return true;
}
bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool flag)
{
if (!owner_id) {
return false;
}
std::vector l = {
Client::booDeathMarquee,
Client::booStatsUpdate,
Client::booSpawnMessageClassSpecific,
Client::booUnused,
Client::booAutoDefend,
Client::booBuffCounter,
Client::booMonkWuMessage
};
if (
std::find(
l.begin(),
l.end(),
static_cast(type)
) != l.end()
) {
return BotOwnerOptionsRepository::ReplaceOne(
database,
BotOwnerOptionsRepository::BotOwnerOptions{
.owner_id = owner_id,
.option_type = static_cast(type),
.option_value = static_cast(flag ? 1 : 0)
}
);
}
return false;
}
bool BotDatabase::SaveOwnerOption(const uint32 owner_id, const std::pair type, const std::pair flag)
{
if (!owner_id) {
return false;
}
std::vector l = {
Client::booSpawnMessageSay,
Client::booSpawnMessageTell
};
auto e = BotOwnerOptionsRepository::NewEntity();
std::vector v;
if (
std::find(
l.begin(),
l.end(),
static_cast(type.first)
) != l.end() &&
std::find(
l.begin(),
l.end(),
static_cast(type.second)
) != l.end()
) {
e.owner_id = owner_id;
e.option_type = static_cast(type.first);
e.option_value = static_cast(flag.first ? 1 : 0);
v.emplace_back(e);
e.option_type = static_cast(type.second);
e.option_value = static_cast(flag.second ? 1 : 0);
v.emplace_back(e);
return BotOwnerOptionsRepository::ReplaceMany(database, v);
}
return false;
}
bool BotDatabase::LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 group_id, std::list& group_list)
{
if (!group_id || !owner_id) {
return false;
}
const auto& l = GroupIdRepository::GetWhere(
database,
fmt::format(
"`group_id` = {} AND `bot_id` != 0 AND `name` IN (SELECT `name` FROM `bot_data` WHERE `owner_id` = {})",
group_id,
owner_id
)
);
for (const auto& e : l) {
group_list.emplace_back(e.bot_id);
}
return true;
}
bool BotDatabase::LoadHealRotationIDByBotID(const uint32 bot_id, uint32& hr_index)
{
if (!bot_id) {
return false;
}
const auto& l = BotHealRotationsRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {} LIMIT 1",
bot_id
)
);
if (l.empty()) {
return true;
}
auto e = l.front();
hr_index = e.heal_rotation_index;
return true;
}
bool BotDatabase::LoadHealRotation(Bot* hr_member, std::list& member_list, std::list& target_list, bool& load_flag, bool& member_fail, bool& target_fail)
{
if (!hr_member) {
return false;
}
uint32 hr_index = 0;
if (!LoadHealRotationIDByBotID(hr_member->GetBotID(), hr_index)) {
return false;
}
if (!hr_index) {
return true;
}
if (!hr_member->IsHealRotationMember()) {
return false;
}
const auto& e = BotHealRotationsRepository::FindOne(database, hr_index);
if (!e.heal_rotation_index) {
return false;
}
auto m = (*hr_member->MemberOfHealRotation());
m->SetIntervalS(e.interval_);
m->SetFastHeals(e.fast_heals);
m->SetAdaptiveTargeting(e.adaptive_targeting);
m->SetCastingOverride(e.casting_override);
m->SetArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN, e.safe_hp_base);
m->SetArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH, e.safe_hp_cloth);
m->SetArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER, e.safe_hp_leather);
m->SetArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN, e.safe_hp_chain);
m->SetArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE, e.safe_hp_plate);
m->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN, e.critical_hp_base);
m->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH, e.critical_hp_cloth);
m->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER, e.critical_hp_leather);
m->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN, e.critical_hp_chain);
m->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE, e.critical_hp_plate);
load_flag = true;
if (!LoadHealRotationMembers(hr_index, member_list)) {
member_fail = true;
}
if (!LoadHealRotationTargets(hr_index, target_list)) {
target_fail = true;
}
return true;
}
bool BotDatabase::LoadHealRotationMembers(const uint32 hr_index, std::list& member_list)
{
if (!hr_index) {
return false;
}
const auto& l = BotHealRotationMembersRepository::GetWhere(
database,
fmt::format(
"`heal_rotation_index` = {}",
hr_index
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
member_list.push_back(e.bot_id);
}
return true;
}
bool BotDatabase::LoadHealRotationTargets(const uint32 hr_index, std::list& target_list)
{
if (!hr_index) {
return false;
}
const auto& l = BotHealRotationTargetsRepository::GetWhere(
database,
fmt::format(
"`heal_rotation_index` = {}",
hr_index
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
target_list.push_back(e.target_name);
}
return true;
}
bool BotDatabase::SaveHealRotation(Bot* hr_member, bool& member_fail, bool& target_fail)
{
if (
!hr_member ||
!DeleteHealRotation(hr_member->GetBotID()) ||
!hr_member->IsHealRotationMember()
) {
return false;
}
auto m = (*hr_member->MemberOfHealRotation());
auto e = BotHealRotationsRepository::NewEntity();
e.bot_id = hr_member->GetBotID();
e.interval_ = m->IntervalS();
e.fast_heals = m->FastHeals();
e.adaptive_targeting = m->AdaptiveTargeting();
e.casting_override = m->CastingOverride();
e.safe_hp_base = m->ArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN);
e.safe_hp_cloth = m->ArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH);
e.safe_hp_leather = m->ArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER);
e.safe_hp_chain = m->ArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN);
e.safe_hp_plate = m->ArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE);
e.critical_hp_base = m->ArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN);
e.critical_hp_cloth = m->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH);
e.critical_hp_leather = m->ArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER);
e.critical_hp_chain = m->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN);
e.critical_hp_plate = m->ArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE);
e = BotHealRotationsRepository::InsertOne(database, e);
if (!e.heal_rotation_index) {
return false;
}
std::list* ml = m->MemberList();
auto re = BotHealRotationMembersRepository::NewEntity();
re.heal_rotation_index = e.heal_rotation_index;
std::vector rv;
for (auto m : *ml) {
if (!m) {
continue;
}
re.bot_id = m->GetBotID();
rv.emplace_back(re);
}
const int inserted_members = BotHealRotationMembersRepository::InsertMany(database, rv);
if (!inserted_members) {
member_fail = true;
}
std::list* tl = m->TargetList();
auto te = BotHealRotationTargetsRepository::NewEntity();
te.heal_rotation_index = e.heal_rotation_index;
std::vector tv;
for (auto m : *tl) {
if (!m) {
continue;
}
te.target_name = m->GetCleanName();
tv.emplace_back(te);
}
const int inserted_targets = BotHealRotationTargetsRepository::InsertMany(database, tv);
if (!inserted_targets) {
target_fail = true;
}
return true;
}
bool BotDatabase::DeleteHealRotation(const uint32 creator_id)
{
if (!creator_id) {
return false;
}
uint32 hr_index = 0;
if (!LoadHealRotationIDByBotID(creator_id, hr_index)) {
return false;
}
if (!hr_index) {
return true;
}
BotHealRotationTargetsRepository::DeleteWhere(
database,
fmt::format(
"`heal_rotation_index` = {}",
hr_index
)
);
BotHealRotationMembersRepository::DeleteWhere(
database,
fmt::format(
"`heal_rotation_index` = {}",
hr_index
)
);
BotHealRotationsRepository::DeleteWhere(
database,
fmt::format(
"`heal_rotation_index` = {}",
hr_index
)
);
return true;
}
bool BotDatabase::DeleteAllHealRotations(const uint32 owner_id)
{
if (!owner_id) {
return false;
}
const auto& l = BotHealRotationsRepository::GetWhere(
database,
fmt::format(
"`bot_id` IN (SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = {})",
owner_id
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
DeleteHealRotation(e.bot_id);
}
return true;
}
/* Bot miscellaneous functions */
uint8 BotDatabase::GetSpellCastingChance(uint8 spell_type_index, uint8 class_index, uint8 stance_index, uint8 conditional_index) // class_index is 0-based
{
if (
spell_type_index >= Bot::SPELL_TYPE_COUNT ||
class_index >= Class::PLAYER_CLASS_COUNT ||
stance_index >= Stance::AEBurn ||
conditional_index >= cntHSND
) {
return 0;
}
return Bot::spell_casting_chances[spell_type_index][class_index][stance_index][conditional_index];
}
uint32 BotDatabase::GetRaceClassBitmask(uint32 bot_race)
{
const auto& e = BotCreateCombinationsRepository::FindOne(database, bot_race);
return e.race ? e.classes : 0;
}
const uint8 BotDatabase::GetBotClassByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.class_ : Class::None;
}
const uint8 BotDatabase::GetBotGenderByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.gender : Gender::Neuter;
}
std::vector BotDatabase::GetBotIDsByCharacterID(const uint32 character_id, uint8 class_id)
{
std::vector v;
const auto& l = BotDataRepository::GetWhere(
database,
fmt::format(
"`owner_id` = {}{}",
character_id,
(
class_id ?
fmt::format(
" AND `class` = {}",
class_id
) :
""
)
)
);
for (const auto& e : l) {
v.push_back(e.bot_id);
}
return v;
}
const uint8 BotDatabase::GetBotLevelByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.level : 0;
}
const std::string BotDatabase::GetBotNameByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.name : std::string();
}
const uint16 BotDatabase::GetBotRaceByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.race : Race::Doug;
}
const int BotDatabase::GetBotExtraHasteByID(const uint32 bot_id)
{
const auto& e = BotDataRepository::FindOne(database, bot_id);
return e.bot_id ? e.extra_haste : 0;
}
bool BotDatabase::LoadBotSettings(Mob* m)
{
if (!m) {
return false;
}
if (!m->IsOfClientBot()) {
return false;
}
uint32 mob_id = (m->IsClient() ? m->CastToClient()->CharacterID() : m->CastToBot()->GetBotID());
uint8 stance_id = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0);
std::string query = "";
if (m->IsClient()) {
query = fmt::format("`character_id` = {} AND `stance` = {}", mob_id, stance_id);
}
else {
query = fmt::format("`bot_id` = {} AND `stance` = {}", mob_id, stance_id);
}
if (stance_id == Stance::Passive) {
LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive);
return true;
}
const auto& l = BotSettingsRepository::GetWhere(database, query);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
if (e.setting_type == BotSettingCategories::BaseSetting) {
LogBotSettings("[{}] says, 'Loading {} [{}] - setting to [{}].",
m->GetCleanName(),
Bot::GetBotSettingCategoryName(e.setting_type),
e.setting_type,
e.value
);
}
else {
LogBotSettings("[{}] says, 'Loading {} [{}], {} [{}] - setting to [{}].",
m->GetCleanName(),
Bot::GetBotSpellCategoryShortName(e.setting_type),
e.setting_type,
Bot::GetSpellTypeNameByID(e.setting_id),
e.setting_id,
e.value
);
}
if (m->IsClient()) {
m->CastToClient()->SetBotSetting(e.setting_type, e.setting_id, e.value);
}
else {
m->CastToBot()->SetBotSetting(e.setting_type, e.setting_id, e.value);
}
}
return true;
}
bool BotDatabase::SaveBotSettings(Mob* m)
{
if (!m) {
return false;
}
if (!m->IsOfClientBot()) {
return false;
}
uint32 bot_id = (m->IsBot() ? m->CastToBot()->GetBotID() : 0);
uint32 character_id = (m->IsClient() ? m->CastToClient()->CharacterID() : 0);
uint8 stance_id = (m->IsBot() ? m->CastToBot()->GetBotStance() : 0);
if (stance_id == Stance::Passive) {
LogBotSettings("{} is currently set to {} [#{}]. No saving or loading required.", m->GetCleanName(), Stance::GetName(Stance::Passive), Stance::Passive);
return true;
}
std::string query = "";
if (m->IsClient()) {
query = fmt::format("`character_id` = {} AND `stance` = {}", character_id, stance_id);
}
else {
query = fmt::format("`bot_id` = {} AND `stance` = {}", bot_id, stance_id);
}
BotSettingsRepository::DeleteWhere(database, query);
std::vector v;
if (m->IsBot()) {
uint8 bot_stance = m->CastToBot()->GetBotStance();
for (uint16 i = BotBaseSettings::START; i <= BotBaseSettings::END; ++i) {
if (m->CastToBot()->GetBotBaseSetting(i) != m->CastToBot()->GetDefaultBotBaseSetting(i, bot_stance)) {
auto e = BotSettingsRepository::BotSettings{
.character_id = character_id,
.bot_id = bot_id,
.stance = stance_id,
.setting_id = static_cast(i),
.setting_type = static_cast(BotSettingCategories::BaseSetting),
.value = static_cast(m->CastToBot()->GetBotBaseSetting(i)),
.category_name = Bot::GetBotSpellCategoryShortName(BotSettingCategories::BaseSetting),
.setting_name = Bot::GetBotSettingCategoryName(i)
};
v.emplace_back(e);
LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSettingCategoryName(i), i, e.value, m->CastToBot()->GetDefaultBotBaseSetting(i));
}
}
for (uint16 i = BotSettingCategories::START_NO_BASE; i <= BotSettingCategories::END; ++i) {
for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) {
if (m->CastToBot()->GetSetting(i, x) != m->CastToBot()->GetDefaultSetting(i, x, bot_stance)) {
auto e = BotSettingsRepository::BotSettings{
.character_id = character_id,
.bot_id = bot_id,
.stance = stance_id,
.setting_id = static_cast(x),
.setting_type = static_cast(i),
.value = m->CastToBot()->GetSetting(i, x),
.category_name = Bot::GetBotSpellCategoryShortName(i),
.setting_name = Bot::GetSpellTypeNameByID(x)
};
v.emplace_back(e);
LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, e.value, m->CastToBot()->GetDefaultSetting(i, x, bot_stance));
}
}
}
}
if (m->IsClient()) {
/* Currently unused
if (m->CastToClient()->GetDefaultBotSettings(BotSettingCategories::BaseSetting, BotBaseSettings::IllusionBlock) != m->CastToClient()->GetIllusionBlock()) { // Only illusion block supported
auto e = BotSettingsRepository::BotSettings{
.character_id = character_id,
.bot_id = bot_id,
.stance = stance_id,
.setting_id = static_cast(BotBaseSettings::IllusionBlock),
.setting_type = static_cast(BotSettingCategories::BaseSetting),
.value = m->CastToClient()->GetIllusionBlock(),
.category_name = Bot::GetBotSpellCategoryShortName(BotSettingCategories::BaseSetting),
.setting_name = Bot::GetBotSettingCategoryName(BotBaseSettings::IllusionBlock)
};
v.emplace_back(e);
LogBotSettings("{} says, 'Saving {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSettingCategoryName(BotBaseSettings::IllusionBlock), BotBaseSettings::IllusionBlock, e.value, m->CastToClient()->GetIllusionBlock());
}
*/
for (uint16 i = BotSettingCategories::START_CLIENT; i <= BotSettingCategories::END_CLIENT; ++i) {
for (uint16 x = BotSpellTypes::START; x <= BotSpellTypes::END; ++x) {
LogBotSettings("{} says, 'Checking {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, m->CastToClient()->GetBotSetting(i, x), m->CastToClient()->GetDefaultBotSettings(i, x));
if (m->CastToClient()->GetBotSetting(i, x) != m->CastToClient()->GetDefaultBotSettings(i, x)) {
auto e = BotSettingsRepository::BotSettings{
.character_id = character_id,
.bot_id = bot_id,
.stance = stance_id,
.setting_id = static_cast(x),
.setting_type = static_cast(i),
.value = m->CastToClient()->GetBotSetting(i, x),
.category_name = Bot::GetBotSpellCategoryShortName(i),
.setting_name = Bot::GetSpellTypeNameByID(x)
};
v.emplace_back(e);
LogBotSettings("{} says, 'Saving {} {} [{}] - set to [{}] default [{}].'", m->GetCleanName(), Bot::GetBotSpellCategoryShortName(i), Bot::GetSpellTypeNameByID(x), x, e.value, m->CastToClient()->GetDefaultBotSettings(i, x));
}
}
}
}
if (!v.empty()) {
const int inserted = BotSettingsRepository::ReplaceMany(database, v);
if (!inserted) {
return false;
}
}
return true;
}
bool BotDatabase::DeleteBotSettings(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotSettingsRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
return true;
}
bool BotDatabase::LoadBotBlockedBuffs(Bot* b)
{
if (!b) {
return false;
}
const auto& l = BotBlockedBuffsRepository::GetWhere(
database,
fmt::format(
"`bot_id` = {}",
b->GetBotID()
)
);
std::vector v;
BotBlockedBuffs t{ };
for (const auto& e : l) {
t.spell_id = e.spell_id;
t.blocked = e.blocked;
t.blocked_pet = e.blocked_pet;
v.push_back(t);
}
if (!v.empty()) {
b->SetBotBlockedBuffs(v);
}
return true;
}
bool BotDatabase::SaveBotBlockedBuffs(Bot* b)
{
if (!b) {
return false;
}
if (!DeleteBotBlockedBuffs(b->GetBotID())) {
return false;
}
std::vector v = b->GetBotBlockedBuffs();
if (v.empty()) {
return true;
}
std::vector l;
if (!v.empty()) {
for (auto& blocked_buff : v) {
if (blocked_buff.blocked == 0 && blocked_buff.blocked_pet == 0) {
continue;
}
auto e = BotBlockedBuffsRepository::BotBlockedBuffs{
.bot_id = b->GetBotID(),
.spell_id = blocked_buff.spell_id,
.blocked = blocked_buff.blocked,
.blocked_pet = blocked_buff.blocked_pet
};
l.push_back(e);
}
if (l.empty()) {
return true;
}
BotBlockedBuffsRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
b->GetBotID()
)
);
const int inserted = BotBlockedBuffsRepository::InsertMany(database, l);
if (!inserted) {
DeleteBotBlockedBuffs(b->GetBotID());
return false;
}
}
return true;
}
bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
{
if (!bot_id) {
return false;
}
BotBlockedBuffsRepository::DeleteWhere(
database,
fmt::format(
"`bot_id` = {}",
bot_id
)
);
return true;
}
void BotDatabase::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.maxlevel
);
}
}
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
uint16 parent_type = Bot::GetParentSpellType(correct_type);
if (s.type == parent_type || s.type == correct_type) {
continue;
}
if (correct_type != parent_type) {
correct_type = parent_type;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
correct_type = Bot::GetParentSpellType(correct_type);
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks(
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
correct_type,
spell_id,
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
}
}
}
void BotDatabase::MapCommandedSpellTypeMinLevels() {
commanded_spell_type_min_levels.clear();
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
for (int i = start; i <= end; ++i) {
if (!Bot::IsValidBotSpellType(i)) {
continue;
}
for (int x = Class::Warrior; x <= Class::Berserker; ++x) {
commanded_spell_type_min_levels[i][x] = {UINT8_MAX, "" };
}
}
auto spell_list = BotSpellsEntriesRepository::All(content_db);
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
auto spell = spells[s.spell_id];
if (spell.target_type == ST_Self) {
continue;
}
int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX;
if (
!EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) ||
!Bot::IsValidBotSpellType(s.type)
) {
continue;
}
for (int i = start; i <= end; ++i) {
if (s.minlevel > commanded_spell_type_min_levels[i][bot_class].min_level) {
continue;
}
if (
i > BotSpellTypes::PARENT_TYPE_END &&
i != s.type &&
Bot::GetParentSpellType(i) != s.type
) {
continue;
}
if (!Bot::IsValidSpellTypeBySpellID(i, s.spell_id)) {
continue;
}
if (s.minlevel < commanded_spell_type_min_levels[i][bot_class].min_level) {
commanded_spell_type_min_levels[i][bot_class].min_level = s.minlevel;
commanded_spell_type_min_levels[i][bot_class].description = StringFormat(
"%s [#%u] - Level %u",
GetClassIDName(bot_class),
bot_class,
s.minlevel
);
}
}
}
}