[Bots] Add Bot-specific Spell Settings. (#2553)

* [Bots] Add Bot-specific Spell Settings.

# Notes
- Allows players to set `priority`, `min_level`, `max_level`, `min_hp`, `max_hp`, and `is_enabled` settings per spell based on targeted bot.
- Lets players disable spells they don't want their bots casting or change the criteria they cast them at if they want.

* Update botspellsai.cpp

* Update 2022_11_19_bot_spell_settings.sql

* Typo.

* Update botspellsai.cpp

* Cleanup and add Reload Methods to Perl/Lua.
This commit is contained in:
Alex King
2022-11-27 14:46:36 -05:00
committed by GitHub
parent f6c5560e9c
commit 2d364e2fd1
16 changed files with 1583 additions and 122 deletions
+308 -8
View File
@@ -25,6 +25,7 @@
#include "lua_parser.h"
#include "../common/strings.h"
#include "../common/say_link.h"
#include "../common/repositories/bot_spell_settings_repository.h"
extern volatile bool is_zone_loaded;
@@ -407,6 +408,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
GetBotOwnerDataBuckets();
GetBotDataBuckets();
LoadBotSpellSettings();
AI_AddBotSpells(GetBotSpellID());
CalcBotStats(false);
@@ -10538,18 +10540,24 @@ bool Bot::GetBotOwnerDataBuckets()
return false;
}
auto query = fmt::format(
const auto query = fmt::format(
"SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'",
Strings::Escape(bot_owner->GetBucketKey())
);
auto results = database.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return false;
}
bot_owner_data_buckets.clear();
if (!results.RowCount()) {
return true;
}
for (auto row : results) {
bot_data_buckets.insert(std::pair<std::string,std::string>(row[0], row[1]));
bot_owner_data_buckets.insert(std::pair<std::string,std::string>(row[0], row[1]));
}
return true;
@@ -10557,16 +10565,22 @@ bool Bot::GetBotOwnerDataBuckets()
bool Bot::GetBotDataBuckets()
{
auto query = fmt::format(
const auto query = fmt::format(
"SELECT `key`, `value` FROM data_buckets WHERE `key` LIKE '{}-%'",
Strings::Escape(GetBucketKey())
);
auto results = database.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return false;
}
bot_data_buckets.clear();
if (!results.RowCount()) {
return true;
}
for (auto row : results) {
bot_data_buckets.insert(std::pair<std::string,std::string>(row[0], row[1]));
}
@@ -10591,7 +10605,7 @@ bool Bot::CheckDataBucket(std::string bucket_name, std::string bucket_value, uin
bucket_name
);
player_value = bot_data_buckets[full_name];
player_value = bot_owner_data_buckets[full_name];
if (player_value.empty()) {
return false;
}
@@ -10635,6 +10649,292 @@ void Bot::SetExpansionBitmask(int expansion_bitmask, bool save)
LoadAAs();
}
bool Bot::AddBotSpellSetting(uint16 spell_id, BotSpellSetting* bs)
{
if (!IsValidSpell(spell_id) || !bs) {
return false;
}
auto obs = GetBotSpellSetting(spell_id);
if (obs) {
return false;
}
auto s = BotSpellSettingsRepository::NewEntity();
s.spell_id = spell_id;
s.bot_id = GetBotID();
s.priority = bs->priority;
s.min_level = bs->min_level;
s.max_level = bs->max_level;
s.min_hp = bs->min_hp;
s.max_hp = bs->max_hp;
s.is_enabled = bs->is_enabled;
const auto& nbs = BotSpellSettingsRepository::InsertOne(content_db, s);
if (!nbs.id) {
return false;
}
LoadBotSpellSettings();
return true;
}
bool Bot::DeleteBotSpellSetting(uint16 spell_id)
{
if (!IsValidSpell(spell_id)) {
return false;
}
auto bs = GetBotSpellSetting(spell_id);
if (!bs) {
return false;
}
BotSpellSettingsRepository::DeleteWhere(
content_db,
fmt::format(
"bot_id = {} AND spell_id = {}",
GetBotID(),
spell_id
)
);
LoadBotSpellSettings();
return true;
}
BotSpellSetting* Bot::GetBotSpellSetting(uint16 spell_id)
{
if (!IsValidSpell(spell_id) || !bot_spell_settings.count(spell_id)) {
return nullptr;
}
auto b = bot_spell_settings.find(spell_id);
if (b != bot_spell_settings.end()) {
return &b->second;
}
return nullptr;
}
void Bot::ListBotSpells()
{
auto bot_owner = GetBotOwner();
if (!bot_owner) {
return;
}
if (AIBot_spells.empty()) {
bot_owner->Message(
Chat::White,
fmt::format(
"{} has no AI Spells.",
GetCleanName()
).c_str()
);
return;
}
auto spell_count = 0;
auto spell_number = 1;
for (const auto& s : AIBot_spells) {
bot_owner->Message(
Chat::White,
fmt::format(
"Spell {} | Spell: {} ({})",
spell_number,
spells[s.spellid].name,
s.spellid
).c_str()
);
bot_owner->Message(
Chat::White,
fmt::format(
"Spell {} | Priority: {} Health: {}",
spell_number,
s.priority,
GetHPString(s.min_hp, s.max_hp)
).c_str()
);
spell_count++;
spell_number++;
}
bot_owner->Message(
Chat::White,
fmt::format(
"{} has {} AI Spell{}.",
GetCleanName(),
spell_count,
spell_count != 1 ? "s" :""
).c_str()
);
}
void Bot::ListBotSpellSettings()
{
auto bot_owner = GetBotOwner();
if (!bot_owner) {
return;
}
if (!bot_spell_settings.size()) {
bot_owner->Message(
Chat::White,
fmt::format(
"{} does not have any spell settings.",
GetCleanName()
).c_str()
);
return;
}
auto setting_count = 0;
auto setting_number = 1;
for (const auto& bs : bot_spell_settings) {
bot_owner->Message(
Chat::White,
fmt::format(
"Setting {} | Spell: {} ({}) Enabled: {}",
setting_number,
spells[bs.first].name,
bs.first,
bs.second.is_enabled ? "Yes" : "No"
).c_str()
);
bot_owner->Message(
Chat::White,
fmt::format(
"Setting {} | Priority: {} Levels: {} Health: {}",
setting_number,
bs.second.priority,
GetLevelString(bs.second.min_level, bs.second.max_level),
GetHPString(bs.second.min_hp, bs.second.max_hp)
).c_str()
);
setting_count++;
setting_number++;
}
bot_owner->Message(
Chat::White,
fmt::format(
"{} has {} spell setting{}.",
GetCleanName(),
setting_count,
setting_count != 1 ? "s" : ""
).c_str()
);
}
void Bot::LoadBotSpellSettings()
{
bot_spell_settings.clear();
auto s = BotSpellSettingsRepository::GetWhere(content_db, fmt::format("bot_id = {}", GetBotID()));
if (s.empty()) {
return;
}
for (const auto& e : s) {
BotSpellSetting b;
b.priority = e.priority;
b.min_level = e.min_level;
b.max_level = e.max_level;
b.min_hp = e.min_hp;
b.max_hp = e.max_hp;
b.is_enabled = e.is_enabled;
bot_spell_settings[e.spell_id] = b;
}
}
bool Bot::UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs)
{
if (!IsValidSpell(spell_id) || !bs) {
return false;
}
auto s = BotSpellSettingsRepository::NewEntity();
s.spell_id = spell_id;
s.bot_id = GetBotID();
s.priority = bs->priority;
s.min_level = bs->min_level;
s.max_level = bs->max_level;
s.min_hp = bs->min_hp;
s.max_hp = bs->max_hp;
s.is_enabled = bs->is_enabled;
auto obs = GetBotSpellSetting(spell_id);
if (!obs) {
return false;
}
if (!BotSpellSettingsRepository::UpdateSpellSetting(content_db, s)) {
return false;
}
LoadBotSpellSettings();
return true;
}
std::string Bot::GetLevelString(uint8 min_level, uint8 max_level)
{
std::string level_string = "Any";
if (min_level && max_level) {
level_string = fmt::format(
"{} to {}",
min_level,
max_level
);
} else if (min_level && !max_level) {
level_string = fmt::format(
"{}+",
min_level
);
} else if (!min_level && max_level) {
level_string = fmt::format(
"1 to {}",
max_level
);
}
return level_string;
}
std::string Bot::GetHPString(int8 min_hp, int8 max_hp)
{
std::string hp_string = "Any";
if (min_hp && max_hp) {
hp_string = fmt::format(
"{}%% to {}%%",
min_hp,
max_hp
);
} else if (min_hp && !max_hp) {
hp_string = fmt::format(
"{}%% to 100%%",
min_hp
);
} else if (!min_hp && max_hp) {
hp_string = fmt::format(
"1%% to {}%%",
max_hp
);
}
return hp_string;
}
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 };
#endif