[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1583 additions and 122 deletions

View File

@ -403,6 +403,7 @@ namespace DatabaseSchema {
"bot_pet_inventories",
"bot_pets",
"bot_spell_casting_chances",
"bot_spell_settings",
"bot_spells_entries",
"bot_stances",
"bot_timers",

View File

@ -0,0 +1,403 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
*/
#ifndef EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H
#define EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseBotSpellSettingsRepository {
public:
struct BotSpellSettings {
uint32_t id;
int32_t bot_id;
int16_t spell_id;
int16_t priority;
uint16_t min_level;
uint16_t max_level;
int16_t min_hp;
int16_t max_hp;
uint8_t is_enabled;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"bot_id",
"spell_id",
"priority",
"min_level",
"max_level",
"min_hp",
"max_hp",
"is_enabled",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"bot_id",
"spell_id",
"priority",
"min_level",
"max_level",
"min_hp",
"max_hp",
"is_enabled",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("bot_spell_settings");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static BotSpellSettings NewEntity()
{
BotSpellSettings e{};
e.id = 0;
e.bot_id = 0;
e.spell_id = 0;
e.priority = 0;
e.min_level = 0;
e.max_level = 255;
e.min_hp = 0;
e.max_hp = 0;
e.is_enabled = 1;
return e;
}
static BotSpellSettings GetBotSpellSettings(
const std::vector<BotSpellSettings> &bot_spell_settingss,
int bot_spell_settings_id
)
{
for (auto &bot_spell_settings : bot_spell_settingss) {
if (bot_spell_settings.id == bot_spell_settings_id) {
return bot_spell_settings;
}
}
return NewEntity();
}
static BotSpellSettings FindOne(
Database& db,
int bot_spell_settings_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
BaseSelect(),
bot_spell_settings_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
BotSpellSettings e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.bot_id = static_cast<int32_t>(atoi(row[1]));
e.spell_id = static_cast<int16_t>(atoi(row[2]));
e.priority = static_cast<int16_t>(atoi(row[3]));
e.min_level = static_cast<uint16_t>(strtoul(row[4], nullptr, 10));
e.max_level = static_cast<uint16_t>(strtoul(row[5], nullptr, 10));
e.min_hp = static_cast<int16_t>(atoi(row[6]));
e.max_hp = static_cast<int16_t>(atoi(row[7]));
e.is_enabled = static_cast<uint8_t>(strtoul(row[8], nullptr, 10));
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int bot_spell_settings_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
bot_spell_settings_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const BotSpellSettings &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.id));
v.push_back(columns[1] + " = " + std::to_string(e.bot_id));
v.push_back(columns[2] + " = " + std::to_string(e.spell_id));
v.push_back(columns[3] + " = " + std::to_string(e.priority));
v.push_back(columns[4] + " = " + std::to_string(e.min_level));
v.push_back(columns[5] + " = " + std::to_string(e.max_level));
v.push_back(columns[6] + " = " + std::to_string(e.min_hp));
v.push_back(columns[7] + " = " + std::to_string(e.max_hp));
v.push_back(columns[8] + " = " + std::to_string(e.is_enabled));
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static BotSpellSettings InsertOne(
Database& db,
BotSpellSettings e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.spell_id));
v.push_back(std::to_string(e.priority));
v.push_back(std::to_string(e.min_level));
v.push_back(std::to_string(e.max_level));
v.push_back(std::to_string(e.min_hp));
v.push_back(std::to_string(e.max_hp));
v.push_back(std::to_string(e.is_enabled));
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.id = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<BotSpellSettings> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.bot_id));
v.push_back(std::to_string(e.spell_id));
v.push_back(std::to_string(e.priority));
v.push_back(std::to_string(e.min_level));
v.push_back(std::to_string(e.max_level));
v.push_back(std::to_string(e.min_hp));
v.push_back(std::to_string(e.max_hp));
v.push_back(std::to_string(e.is_enabled));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<BotSpellSettings> All(Database& db)
{
std::vector<BotSpellSettings> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
BotSpellSettings e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.bot_id = static_cast<int32_t>(atoi(row[1]));
e.spell_id = static_cast<int16_t>(atoi(row[2]));
e.priority = static_cast<int16_t>(atoi(row[3]));
e.min_level = static_cast<uint16_t>(strtoul(row[4], nullptr, 10));
e.max_level = static_cast<uint16_t>(strtoul(row[5], nullptr, 10));
e.min_hp = static_cast<int16_t>(atoi(row[6]));
e.max_hp = static_cast<int16_t>(atoi(row[7]));
e.is_enabled = static_cast<uint8_t>(strtoul(row[8], nullptr, 10));
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<BotSpellSettings> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<BotSpellSettings> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
BotSpellSettings e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.bot_id = static_cast<int32_t>(atoi(row[1]));
e.spell_id = static_cast<int16_t>(atoi(row[2]));
e.priority = static_cast<int16_t>(atoi(row[3]));
e.min_level = static_cast<uint16_t>(strtoul(row[4], nullptr, 10));
e.max_level = static_cast<uint16_t>(strtoul(row[5], nullptr, 10));
e.min_hp = static_cast<int16_t>(atoi(row[6]));
e.max_hp = static_cast<int16_t>(atoi(row[7]));
e.is_enabled = static_cast<uint8_t>(strtoul(row[8], nullptr, 10));
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
};
#endif //EQEMU_BASE_BOT_SPELL_SETTINGS_REPOSITORY_H

View File

@ -0,0 +1,77 @@
#ifndef EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H
#define EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_bot_spell_settings_repository.h"
class BotSpellSettingsRepository: public BaseBotSpellSettingsRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* BotSpellSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* BotSpellSettingsRepository::GetWhereNeverExpires()
* BotSpellSettingsRepository::GetWhereXAndY()
* BotSpellSettingsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
static bool UpdateSpellSetting(
Database& db,
const BotSpellSettings &e
) {
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[3] + " = " + std::to_string(e.priority));
v.push_back(columns[4] + " = " + std::to_string(e.min_level));
v.push_back(columns[5] + " = " + std::to_string(e.max_level));
v.push_back(columns[6] + " = " + std::to_string(e.min_hp));
v.push_back(columns[7] + " = " + std::to_string(e.max_hp));
v.push_back(columns[8] + " = " + std::to_string(e.is_enabled));
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE `bot_id` = {} AND `spell_id` = {}",
TableName(),
Strings::Implode(", ", v),
e.bot_id,
e.spell_id
)
);
return (results.Success() ? true : false);
}
};
#endif //EQEMU_BOT_SPELL_SETTINGS_REPOSITORY_H

View File

@ -37,7 +37,7 @@
#define CURRENT_BINARY_DATABASE_VERSION 9212
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9032
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9033
#else
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0
#endif

View File

@ -31,6 +31,7 @@
9030|2022_10_27_bot_data_buckets.sql|SHOW COLUMNS FROM `bot_spells_entries` LIKE 'bucket_name'|empty|
9031|2022_11_13_bot_spells_entries.sql|SELECT * FROM db_version WHERE bots_version >= 9031|empty|
9032|2022_11_07_bot_expansion_bitmask.sql|SHOW COLUMNS FROM `bot_data` LIKE 'expansion_bitmask'|empty|
9033|2022_11_19_bot_spell_settings.sql|SHOW TABLES LIKE 'bot_spell_settings'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,12 @@
CREATE TABLE `bot_spell_settings` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`bot_id` int(11) NOT NULL DEFAULT 0,
`spell_id` smallint(5) NOT NULL DEFAULT 0,
`priority` smallint(5) NOT NULL DEFAULT 0,
`min_level` smallint(5) unsigned NOT NULL DEFAULT 0,
`max_level` smallint(5) unsigned NOT NULL DEFAULT 255,
`min_hp` smallint(5) NOT NULL DEFAULT 0,
`max_hp` smallint(5) NOT NULL DEFAULT 0,
`is_enabled` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

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

View File

@ -307,7 +307,7 @@ public:
void DoEnduranceRegen(); //This Regenerates endurance
void DoEnduranceUpkeep(); //does the endurance upkeep
bool AI_AddBotSpells(uint32 iDBSpellsID);
bool AI_AddBotSpells(uint32 bot_spell_id);
void AddSpellToBotList(
int16 iPriority,
uint16 iSpellID,
@ -470,6 +470,7 @@ public:
//static void UpdateRaidCastingRoles(const Raid* raid, bool disband = false);
bool IsBotCaster() { return IsCasterClass(GetClass()); }
bool IsBotHybrid() { return IsHybridClass(GetClass()); }
bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); }
bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); }
bool IsBotSpellFighter() { return IsSpellFighterClass(GetClass()); }
@ -596,6 +597,18 @@ public:
int GetExpansionBitmask();
void SetExpansionBitmask(int expansion_bitmask, bool save = true);
void ListBotSpells();
std::string GetLevelString(uint8 min_level, uint8 max_level);
std::string GetHPString(int8 min_hp, int8 max_hp);
bool AddBotSpellSetting(uint16 spell_id, BotSpellSetting* bs);
bool DeleteBotSpellSetting(uint16 spell_id);
BotSpellSetting* GetBotSpellSetting(uint16 spell_id);
void ListBotSpellSettings();
void LoadBotSpellSettings();
bool UpdateBotSpellSetting(uint16 spell_id, BotSpellSetting* bs);
static void SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leader_id);
std::string CreateSayLink(Client* botOwner, const char* message, const char* name);
@ -762,6 +775,9 @@ private:
BotCastingRoles m_CastingRoles;
std::map<std::string,std::string> bot_data_buckets;
std::map<std::string,std::string> bot_owner_data_buckets;
std::map<uint16, BotSpellSetting> bot_spell_settings;
std::shared_ptr<HealRotation> m_member_of_heal_rotation;

View File

@ -1416,6 +1416,12 @@ int bot_command_init(void)
bot_command_add("rune", "Orders a bot to cast a rune of protection", AccountStatus::Player, bot_command_rune) ||
bot_command_add("sendhome", "Orders a bot to open a magical doorway home", AccountStatus::Player, bot_command_send_home) ||
bot_command_add("size", "Orders a bot to change a player's size", AccountStatus::Player, bot_command_size) ||
bot_command_add("spelllist", "Lists a Caster of Hybrid bot's spells", AccountStatus::Player, bot_command_spell_list) ||
bot_command_add("spellsettingsadd", "Add a bot spell setting for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_add) ||
bot_command_add("spellsettingsdelete", "Delete a bot spell setting from a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_delete) ||
bot_command_add("spellsettingslist", "Lists a Caster or Hybrid bot's spell settings", AccountStatus::Player, bot_command_spell_settings_list) ||
bot_command_add("spellsettingstoggle", "Toggle a bot spell for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_toggle) ||
bot_command_add("spellsettingsupdate", "Update a bot spell setting for a Caster or Hybrid bot", AccountStatus::Player, bot_command_spell_settings_update) ||
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) ||
@ -10086,7 +10092,7 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b
if (local_entry->single != single_flag) {
continue;
}
msg = fmt::format(
"{}circle {}{}",
std::to_string(BOT_COMMAND_CHAR),
@ -10122,7 +10128,7 @@ void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_b
if (local_entry->single != single_flag) {
continue;
}
msg = fmt::format(
"{}portal {}{}",
std::to_string(BOT_COMMAND_CHAR),
@ -10226,4 +10232,514 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp
return false;
}
void bot_command_spell_list(Client* c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_list", sep->arg[0], "spelllist")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
my_bot->ListBotSpells();
}
void bot_command_spell_settings_add(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_settings_add", sep->arg[0], "spellsettingsadd")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
auto arguments = sep->argnum;
if (
arguments < 6 ||
!sep->IsNumber(1) ||
!sep->IsNumber(2) ||
!sep->IsNumber(3) ||
!sep->IsNumber(4) ||
!sep->IsNumber(5) ||
!sep->IsNumber(6)
) {
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]",
sep->arg[0]
).c_str()
);
return;
}
auto spell_id = static_cast<uint16>(std::stoul(sep->arg[1]));
if (!IsValidSpell(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"Spell ID {} is invalid or could not be found.",
spell_id
).c_str()
);
return;
}
if (my_bot->GetBotSpellSetting(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"{} already has a spell setting for {} ({}), trying using {} instead.",
my_bot->GetCleanName(),
spells[spell_id].name,
spell_id,
Saylink::Silent("^spellsettingsupdate")
).c_str()
);
return;
}
auto priority = static_cast<int16>(std::stoi(sep->arg[2]));
auto min_level = static_cast<uint8>(std::stoul(sep->arg[3]));
auto max_level = static_cast<uint8>(std::stoul(sep->arg[4]));
auto min_hp = static_cast<int8>(EQ::Clamp(std::stoi(sep->arg[5]), -1, 99));
auto max_hp = static_cast<int8>(EQ::Clamp(std::stoi(sep->arg[6]), -1, 100));
BotSpellSetting bs;
bs.priority = priority;
bs.min_level = min_level;
bs.max_level = max_level;
bs.min_hp = min_hp;
bs.max_hp = max_hp;
if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) {
c->Message(
Chat::White,
fmt::format(
"Failed to add spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
return;
}
my_bot->AI_AddBotSpells(my_bot->GetBotSpellID());
c->Message(
Chat::White,
fmt::format(
"Successfully added spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell Setting Added | Spell: {} ({}) ",
spells[spell_id].name,
spell_id
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell Setting Added | Priority: {} Levels: {} Health: {}",
priority,
my_bot->GetLevelString(min_level, max_level),
my_bot->GetHPString(min_hp, max_hp)
).c_str()
);
}
void bot_command_spell_settings_delete(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_settings_delete", sep->arg[0], "spellsettingsdelete")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID]",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
auto arguments = sep->argnum;
if (
arguments < 1 ||
!sep->IsNumber(1)
) {
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID]",
sep->arg[0]
).c_str()
);
return;
}
auto spell_id = static_cast<uint16>(std::stoul(sep->arg[1]));
if (!IsValidSpell(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"Spell ID {} is invalid or could not be found.",
spell_id
).c_str()
);
return;
}
if (!my_bot->DeleteBotSpellSetting(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"Failed to delete spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
return;
}
my_bot->AI_AddBotSpells(my_bot->GetBotSpellID());
c->Message(
Chat::White,
fmt::format(
"Successfully deleted spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell Setting Deleted | Spell: {} ({})",
spells[spell_id].name,
spell_id
).c_str()
);
}
void bot_command_spell_settings_list(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_settings_list", sep->arg[0], "spellsettingslist")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {}",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
my_bot->ListBotSpellSettings();
}
void bot_command_spell_settings_toggle(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_settings_toggle", sep->arg[0], "spellsettingstoggle")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Toggle]",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
auto arguments = sep->argnum;
if (
arguments < 2 ||
!sep->IsNumber(1)
) {
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Toggle]",
sep->arg[0]
).c_str()
);
return;
}
auto spell_id = static_cast<uint16>(std::stoul(sep->arg[1]));
if (!IsValidSpell(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"Spell ID {} is invalid or could not be found.",
spell_id
).c_str()
);
return;
}
bool toggle = (
sep->IsNumber(2) ?
(std::stoi(sep->arg[2]) ? true : false) :
atobool(sep->arg[2])
);
auto obs = my_bot->GetBotSpellSetting(spell_id);
if (!obs) {
return;
}
BotSpellSetting bs;
bs.priority = obs->priority;
bs.min_level = obs->min_level;
bs.max_level = obs->max_level;
bs.min_hp = obs->min_hp;
bs.max_hp = obs->max_hp;
bs.is_enabled = toggle;
if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) {
c->Message(
Chat::White,
fmt::format(
"Failed to {}able spell for {}.",
toggle ? "en" : "dis",
my_bot->GetCleanName()
).c_str()
);
return;
}
my_bot->AI_AddBotSpells(my_bot->GetBotSpellID());
c->Message(
Chat::White,
fmt::format(
"Successfully {}abled spell for {}.",
toggle ? "en" : "dis",
my_bot->GetCleanName()
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell {}abled | Spell: {} ({})",
toggle ? "En" : "Dis",
spells[spell_id].name,
spell_id
).c_str()
);
}
void bot_command_spell_settings_update(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_spell_settings_update", sep->arg[0], "spellsettingsupdate")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]",
sep->arg[0]
).c_str()
);
return;
}
auto my_bot = ActionableBots::AsTarget_ByBot(c);
if (!my_bot) {
c->Message(Chat::White, "You must target a bot that you own to use this command.");
return;
}
if (!my_bot->IsBotCaster() && !my_bot->IsBotHybrid()) {
c->Message(Chat::White, "You must target a Caster or Hybrid bot to use this command.");
return;
}
auto arguments = sep->argnum;
if (
arguments < 6 ||
!sep->IsNumber(1) ||
!sep->IsNumber(2) ||
!sep->IsNumber(3) ||
!sep->IsNumber(4) ||
!sep->IsNumber(5) ||
!sep->IsNumber(6)
) {
c->Message(
Chat::White,
fmt::format(
"Usage: {} [Spell ID] [Priority] [Min Level] [Max Level] [Min HP] [Max HP]",
sep->arg[0]
).c_str()
);
return;
}
auto spell_id = static_cast<uint16>(std::stoul(sep->arg[1]));
if (!IsValidSpell(spell_id)) {
c->Message(
Chat::White,
fmt::format(
"Spell ID {} is invalid or could not be found.",
spell_id
).c_str()
);
return;
}
auto priority = static_cast<int16>(std::stoi(sep->arg[2]));
auto min_level = static_cast<uint8>(std::stoul(sep->arg[3]));
auto max_level = static_cast<uint8>(std::stoul(sep->arg[4]));
auto min_hp = static_cast<int8>(EQ::Clamp(std::stoi(sep->arg[5]), -1, 99));
auto max_hp = static_cast<int8>(EQ::Clamp(std::stoi(sep->arg[6]), -1, 100));
BotSpellSetting bs;
bs.priority = priority;
bs.min_level = min_level;
bs.max_level = max_level;
bs.min_hp = min_hp;
bs.max_hp = max_hp;
if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) {
c->Message(
Chat::White,
fmt::format(
"Failed to update spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
return;
}
my_bot->AI_AddBotSpells(my_bot->GetBotSpellID());
c->Message(
Chat::White,
fmt::format(
"Successfully updated spell setting for {}.",
my_bot->GetCleanName()
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell Setting Updated | Spell: {} ({})",
spells[spell_id].name,
spell_id
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"Spell Setting Updated | Priority: {} Levels: {} Health: {}",
priority,
my_bot->GetLevelString(min_level, max_level),
my_bot->GetHPString(min_hp, max_hp)
).c_str()
);
}
#endif // BOTS

View File

@ -526,7 +526,6 @@ typedef std::map<BCEnum::SpType, std::map<uint8, std::string>> bcst_required_bot
typedef std::map<uint8, uint8> bcst_levels;
typedef std::map<BCEnum::SpType, bcst_levels> bcst_levels_map;
#define BOT_COMMAND_CHAR '^'
typedef void (*BotCmdFuncPtr)(Client *,const Seperator *);
@ -540,8 +539,7 @@ typedef struct {
extern int (*bot_command_dispatch)(Client *,char const*);
extern int bot_command_count; // number of bot commands loaded
// the bot command system:
// Bot Command System:
int bot_command_init(void);
void bot_command_deinit(void);
int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function);
@ -549,8 +547,7 @@ int bot_command_not_avail(Client *c, const char *message);
int bot_command_real_dispatch(Client *c, char const *message);
void bot_command_log_command(Client *c, const char *message);
// bot commands
// Bot Commands
void bot_command_actionable(Client *c, const Seperator *sep);
void bot_command_aggressive(Client *c, const Seperator *sep);
void bot_command_apply_poison(Client *c, const Seperator *sep);
@ -589,6 +586,12 @@ void bot_command_resurrect(Client *c, const Seperator *sep);
void bot_command_rune(Client *c, const Seperator *sep);
void bot_command_send_home(Client *c, const Seperator *sep);
void bot_command_size(Client *c, const Seperator *sep);
void bot_command_spell_list(Client* c, const Seperator *sep);
void bot_command_spell_settings_add(Client* c, const Seperator *sep);
void bot_command_spell_settings_delete(Client* c, const Seperator *sep);
void bot_command_spell_settings_list(Client* c, const Seperator *sep);
void bot_command_spell_settings_toggle(Client* c, const Seperator *sep);
void bot_command_spell_settings_update(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);
@ -596,8 +599,7 @@ 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);
// bot subcommands
// Bot Subcommands
void bot_subcommand_bot_appearance(Client *c, const Seperator *sep);
void bot_subcommand_bot_beard_color(Client *c, const Seperator *sep);
void bot_subcommand_bot_beard_style(Client *c, const Seperator *sep);

View File

@ -79,6 +79,15 @@ struct BotAA {
uint8 total_levels;
};
struct BotSpellSetting {
int16 priority;
uint8 min_level;
uint8 max_level;
int8 min_hp;
int8 max_hp;
bool is_enabled;
};
#endif // BOTS
#endif // BOT_STRUCTS

View File

@ -21,6 +21,8 @@
#include "bot.h"
#include "../common/data_verification.h"
#include "../common/strings.h"
#include "../common/repositories/bot_spells_entries_repository.h"
#include "../common/repositories/npc_spells_repository.h"
#if EQDEBUG >= 12
#define BotAI_DEBUG_Spells 25
@ -2877,16 +2879,16 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
}
bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
bool Bot::AI_AddBotSpells(uint32 bot_spell_id) {
// ok, this function should load the list, and the parent list then shove them into the struct and sort
npc_spells_id = iDBSpellsID;
npc_spells_id = bot_spell_id;
AIBot_spells.clear();
if (!iDBSpellsID) {
if (!bot_spell_id) {
AIautocastspell_timer->Disable();
return false;
}
auto* spell_list = content_db.GetBotSpells(iDBSpellsID);
auto* spell_list = content_db.GetBotSpells(bot_spell_id);
if (!spell_list) {
AIautocastspell_timer->Disable();
return false;
@ -2897,7 +2899,7 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
auto debug_msg = fmt::format(
"Loading NPCSpells onto {}: dbspellsid={}, level={}",
GetName(),
iDBSpellsID,
bot_spell_id,
GetLevel()
);
@ -2993,7 +2995,50 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
continue;
}
}
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp, e.bucket_name, e.bucket_value, e.bucket_comparison);
const auto& bs = GetBotSpellSetting(e.spellid);
if (bs) {
if (!bs->is_enabled) {
continue;
}
if (bs->min_level > 0 && GetLevel() < bs->min_level) {
continue;
}
if (bs->max_level > 0 && GetLevel() > bs->max_level) {
continue;
}
AddSpellToBotList(
bs->priority,
e.spellid,
e.type,
e.manacost,
e.recast_delay,
e.resist_adjust,
bs->min_hp,
bs->max_hp,
e.bucket_name,
e.bucket_value,
e.bucket_comparison
);
continue;
}
AddSpellToBotList(
e.priority,
e.spellid,
e.type,
e.manacost,
e.recast_delay,
e.resist_adjust,
e.min_hp,
e.max_hp,
e.bucket_name,
e.bucket_value,
e.bucket_comparison
);
}
}
}
@ -3049,7 +3094,50 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
continue;
}
}
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp, e.bucket_name, e.bucket_value, e.bucket_comparison);
const auto& bs = GetBotSpellSetting(e.spellid);
if (bs) {
if (!bs->is_enabled) {
continue;
}
if (bs->min_level > 0 && GetLevel() < bs->min_level) {
continue;
}
if (bs->max_level > 0 && GetLevel() > bs->max_level) {
continue;
}
AddSpellToBotList(
bs->priority,
e.spellid,
e.type,
e.manacost,
e.recast_delay,
e.resist_adjust,
bs->min_hp,
bs->max_hp,
e.bucket_name,
e.bucket_value,
e.bucket_comparison
);
continue;
}
AddSpellToBotList(
e.priority,
e.spellid,
e.type,
e.manacost,
e.recast_delay,
e.resist_adjust,
e.min_hp,
e.max_hp,
e.bucket_name,
e.bucket_value,
e.bucket_comparison
);
}
}
@ -3108,108 +3196,91 @@ bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) {
return it != spell_list->entries.end();
}
DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
DBbotspells_Struct* ZoneDatabase::GetBotSpells(uint32 bot_spell_id)
{
if (!iDBSpellsID) {
if (!bot_spell_id) {
return nullptr;
}
auto it = Bot_Spells_Cache.find(iDBSpellsID);
if (it != Bot_Spells_Cache.end()) { // it's in the cache, easy =)
return &it->second;
auto c = bot_spells_cache.find(bot_spell_id);
if (c != bot_spells_cache.end()) { // it's in the cache, easy =)
return &c->second;
}
if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already
Bot_Spells_LoadTried.insert(iDBSpellsID);
if (!bot_spells_loadtried.count(bot_spell_id)) { // no reason to ask the DB again if we have failed once already
bot_spells_loadtried.insert(bot_spell_id);
auto query = fmt::format(
"SELECT id, parent_list, attack_proc, proc_chance, "
"range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, "
"idle_b_chance FROM npc_spells WHERE id = {}",
iDBSpellsID
);
auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
auto n = NpcSpellsRepository::FindOne(content_db, bot_spell_id);
if (!n.id) {
return nullptr;
}
auto row = results.begin();
DBbotspells_Struct spell_set;
spell_set.parent_list = std::stoul(row[1]);
spell_set.attack_proc = static_cast<uint16>(std::stoul(row[2]));
spell_set.proc_chance = static_cast<uint8>(std::stoul(row[3]));
spell_set.range_proc = static_cast<uint16>(std::stoul(row[4]));
spell_set.rproc_chance = static_cast<int16>(std::stoi(row[5]));
spell_set.defensive_proc = static_cast<uint16>(std::stoul(row[6]));
spell_set.dproc_chance = static_cast<int16>(std::stoi(row[7]));
spell_set.fail_recast = std::stoul(row[8]);
spell_set.engaged_no_sp_recast_min = std::stoul(row[9]);
spell_set.engaged_no_sp_recast_max = std::stoul(row[10]);
spell_set.engaged_beneficial_self_chance = static_cast<uint8>(std::stoul(row[11]));
spell_set.engaged_beneficial_other_chance = static_cast<uint8>(std::stoul(row[12]));
spell_set.engaged_detrimental_chance = static_cast<uint8>(std::stoul(row[13]));
spell_set.pursue_no_sp_recast_min = std::stoul(row[14]);
spell_set.pursue_no_sp_recast_max = std::stoul(row[15]);
spell_set.pursue_detrimental_chance = static_cast<uint8>(std::stoul(row[16]));
spell_set.idle_no_sp_recast_min = std::stoul(row[17]);
spell_set.idle_no_sp_recast_max = std::stoul(row[18]);
spell_set.idle_beneficial_chance = static_cast<uint8>(std::stoul(row[19]));
spell_set.parent_list = n.parent_list;
spell_set.attack_proc = n.attack_proc;
spell_set.proc_chance = n.proc_chance;
spell_set.range_proc = n.range_proc;
spell_set.rproc_chance = n.rproc_chance;
spell_set.defensive_proc = n.defensive_proc;
spell_set.dproc_chance = n.dproc_chance;
spell_set.fail_recast = n.fail_recast;
spell_set.engaged_no_sp_recast_min = n.engaged_no_sp_recast_min;
spell_set.engaged_no_sp_recast_max = n.engaged_no_sp_recast_max;
spell_set.engaged_beneficial_self_chance = n.engaged_b_self_chance;
spell_set.engaged_beneficial_other_chance = n.engaged_b_other_chance;
spell_set.engaged_detrimental_chance = n.engaged_d_chance;
spell_set.pursue_no_sp_recast_min = n.pursue_no_sp_recast_min;
spell_set.pursue_no_sp_recast_max = n.pursue_no_sp_recast_max;
spell_set.pursue_detrimental_chance = n.pursue_d_chance;
spell_set.idle_no_sp_recast_min = n.idle_no_sp_recast_min;
spell_set.idle_no_sp_recast_max = n.idle_no_sp_recast_max;
spell_set.idle_beneficial_chance = n.idle_b_chance;
// pulling fixed values from an auto-increment field is dangerous...
query = fmt::format(
"SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust, "
"bucket_name, bucket_value, bucket_comparison "
"FROM bot_spells_entries "
"WHERE npc_spells_id = {} ORDER BY minlevel",
iDBSpellsID
auto bse = BotSpellsEntriesRepository::GetWhere(
content_db,
fmt::format(
"npc_spells_id = {}",
bot_spell_id
)
);
results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
if (bse.empty()) {
return nullptr;
}
for (auto row : results) {
for (const auto& e : bse) {
DBbotspells_entries_Struct entry;
auto spell_id = std::stoi(row[0]);
entry.spellid = spell_id;
entry.type = std::stoul(row[1]);
entry.minlevel = static_cast<uint8>(std::stoul(row[2]));
entry.maxlevel = static_cast<uint8>(std::stoul(row[3]));
entry.manacost = static_cast<int16>(std::stoi(row[4]));
entry.recast_delay = std::stoi(row[5]);
entry.priority = static_cast<int16>(std::stoi(row[6]));
entry.min_hp = static_cast<int8>(std::stoi(row[7]));
entry.max_hp = static_cast<int8>(std::stoi(row[8]));
entry.resist_adjust = static_cast<int16>(std::stoi(row[9]));
entry.bucket_name = row[10];
entry.bucket_value = row[11];
entry.bucket_comparison = static_cast<uint8>(std::stoul(row[12]));
entry.spellid = e.spellid;
entry.type = e.type;
entry.minlevel = e.minlevel;
entry.maxlevel = e.maxlevel;
entry.manacost = e.manacost;
entry.recast_delay = e.recast_delay;
entry.priority = e.priority;
entry.min_hp = e.min_hp;
entry.max_hp = e.max_hp;
entry.resist_adjust = e.resist_adjust;
entry.bucket_name = e.bucket_name;
entry.bucket_value = e.bucket_value;
entry.bucket_comparison = e.bucket_comparison;
// some spell types don't make much since to be priority 0, so fix that
if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) {
entry.priority = 1;
}
if (row[9]) {
entry.resist_adjust = static_cast<int16>(std::stoi(row[9]));
} else if (IsValidSpell(spell_id)) {
entry.resist_adjust = spells[spell_id].resist_difficulty;
if (e.resist_adjust) {
entry.resist_adjust = e.resist_adjust;
} else if (IsValidSpell(e.spellid)) {
entry.resist_adjust = spells[e.spellid].resist_difficulty;
}
spell_set.entries.push_back(entry);
}
Bot_Spells_Cache.insert(std::make_pair(iDBSpellsID, spell_set));
bot_spells_cache.insert(std::make_pair(bot_spell_id, spell_set));
return &Bot_Spells_Cache[iDBSpellsID];
return &bot_spells_cache[bot_spell_id];
}
return nullptr;

View File

@ -109,6 +109,26 @@ void Lua_Bot::SetExpansionBitmask(int expansion_bitmask, bool save) {
self->SetExpansionBitmask(expansion_bitmask, save);
}
bool Lua_Bot::ReloadBotDataBuckets() {
Lua_Safe_Call_Bool();
return self->GetBotDataBuckets();
}
bool Lua_Bot::ReloadBotOwnerDataBuckets() {
Lua_Safe_Call_Bool();
return self->GetBotOwnerDataBuckets();
}
bool Lua_Bot::ReloadBotSpells() {
Lua_Safe_Call_Bool();
return self->AI_AddBotSpells(self->GetBotSpellID());
}
void Lua_Bot::ReloadBotSpellSettings() {
Lua_Safe_Call_Void();
self->LoadBotSpellSettings();
}
bool Lua_Bot::HasBotSpellEntry(uint16 spellid) {
Lua_Safe_Call_Bool();
return self->HasBotSpellEntry(spellid);
@ -117,27 +137,31 @@ bool Lua_Bot::HasBotSpellEntry(uint16 spellid) {
luabind::scope lua_register_bot() {
return luabind::class_<Lua_Bot, Lua_Mob>("Bot")
.def(luabind::constructor<>())
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16, uint32, int16, bool, uint32, uint32, uint32, uint32, uint32, uint32)) & Lua_Bot::AddBotItem)
.def("CountBotItem", (uint32(Lua_Bot::*)(uint32)) & Lua_Bot::CountBotItem)
.def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16)) & Lua_Bot::GetBotItem)
.def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16)) & Lua_Bot::GetBotItemIDBySlot)
.def("GetExpansionBitmask", (int(Lua_Bot::*)(void)) & Lua_Bot::GetExpansionBitmask)
.def("GetOwner", (Lua_Mob(Lua_Bot::*)(void)) & Lua_Bot::GetOwner)
.def("HasBotItem", (bool(Lua_Bot::*)(uint32)) & Lua_Bot::HasBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem)
.def("AddBotItem", (void(Lua_Bot::*)(uint16,uint32,int16,bool,uint32,uint32,uint32,uint32,uint32,uint32))&Lua_Bot::AddBotItem)
.def("CountBotItem", (uint32(Lua_Bot::*)(uint32))&Lua_Bot::CountBotItem)
.def("GetBotItem", (Lua_ItemInst(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItem)
.def("GetBotItemIDBySlot", (uint32(Lua_Bot::*)(uint16))&Lua_Bot::GetBotItemIDBySlot)
.def("GetExpansionBitmask", (int(Lua_Bot::*)(void))&Lua_Bot::GetExpansionBitmask)
.def("GetOwner", (Lua_Mob(Lua_Bot::*)(void))&Lua_Bot::GetOwner)
.def("HasBotItem", (bool(Lua_Bot::*)(uint32))&Lua_Bot::HasBotItem)
.def("HasBotSpellEntry", (bool(Lua_Bot::*)(uint16)) & Lua_Bot::HasBotSpellEntry)
.def("OwnerMessage", (void(Lua_Bot::*)(std::string)) & Lua_Bot::OwnerMessage)
.def("RemoveBotItem", (void(Lua_Bot::*)(uint32)) & Lua_Bot::RemoveBotItem)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int)) & Lua_Bot::SetExpansionBitmask)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int, bool)) & Lua_Bot::SetExpansionBitmask)
.def("SignalBot", (void(Lua_Bot::*)(int)) & Lua_Bot::SignalBot);
.def("OwnerMessage", (void(Lua_Bot::*)(std::string))&Lua_Bot::OwnerMessage)
.def("ReloadBotDataBuckets", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotDataBuckets)
.def("ReloadBotOwnerDataBuckets", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotOwnerDataBuckets)
.def("ReloadBotSpells", (bool(Lua_Bot::*)(void))&Lua_Bot::ReloadBotSpells)
.def("ReloadBotSpellSettings", (void(Lua_Bot::*)(void))&Lua_Bot::ReloadBotSpellSettings)
.def("RemoveBotItem", (void(Lua_Bot::*)(uint32))&Lua_Bot::RemoveBotItem)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int))&Lua_Bot::SetExpansionBitmask)
.def("SetExpansionBitmask", (void(Lua_Bot::*)(int,bool))&Lua_Bot::SetExpansionBitmask)
.def("SignalBot", (void(Lua_Bot::*)(int))&Lua_Bot::SignalBot);
}
#endif

View File

@ -43,6 +43,10 @@ public:
Lua_Mob GetOwner();
bool HasBotItem(uint32 item_id);
void OwnerMessage(std::string message);
bool ReloadBotDataBuckets();
bool ReloadBotOwnerDataBuckets();
bool ReloadBotSpells();
void ReloadBotSpellSettings();
void RemoveBotItem(uint32 item_id);
void SetExpansionBitmask(int expansion_bitmask);
void SetExpansionBitmask(int expansion_bitmask, bool save);

View File

@ -106,6 +106,26 @@ void Perl_Bot_SetExpansionBitmask(Bot* self, int expansion_bitmask, bool save)
self->SetExpansionBitmask(expansion_bitmask, save);
}
bool Perl_Bot_ReloadBotDataBuckets(Bot* self)
{
return self->GetBotDataBuckets();
}
bool Perl_Bot_ReloadBotOwnerDataBuckets(Bot* self)
{
return self->GetBotOwnerDataBuckets();
}
bool Perl_Bot_ReloadBotSpells(Bot* self)
{
return self->AI_AddBotSpells(self->GetBotSpellID());
}
void Perl_Bot_ReloadBotSpellSettings(Bot* self)
{
self->LoadBotSpellSettings();
}
bool Perl_Bot_HasBotSpellEntry(Bot* self, uint16 spellid)
{
return self->HasBotSpellEntry(spellid);
@ -134,6 +154,10 @@ void perl_register_bot()
package.add("HasBotItem", &Perl_Bot_HasBotItem);
package.add("HasBotSpellEntry", &Perl_Bot_HasBotSpellEntry);
package.add("OwnerMessage", &Perl_Bot_OwnerMessage);
package.add("ReloadBotDataBuckets", &Perl_Bot_ReloadBotDataBuckets);
package.add("ReloadBotOwnerDataBuckets", &Perl_Bot_ReloadBotOwnerDataBuckets);
package.add("ReloadBotSpells", &Perl_Bot_ReloadBotSpells);
package.add("ReloadBotSpellSettings", &Perl_Bot_ReloadBotSpellSettings);
package.add("RemoveBotItem", &Perl_Bot_RemoveBotItem);
package.add("SetExpansionBitmask", (void(*)(Bot*, int))&Perl_Bot_SetExpansionBitmask);
package.add("SetExpansionBitmask", (void(*)(Bot*, int, bool))&Perl_Bot_SetExpansionBitmask);

View File

@ -533,8 +533,8 @@ public:
const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false);
/*Bots */
DBbotspells_Struct* GetBotSpells(uint32 iDBSpellsID);
void ClearBotSpells() { Bot_Spells_Cache.clear(); Bot_Spells_LoadTried.clear(); }
DBbotspells_Struct* GetBotSpells(uint32 bot_spell_id);
void ClearBotSpells() { bot_spells_cache.clear(); bot_spells_loadtried.clear(); }
/* Mercs */
const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel);
@ -637,8 +637,9 @@ protected:
std::unordered_set<uint32> npc_spells_loadtried;
DBnpcspellseffects_Struct** npc_spellseffects_cache;
bool* npc_spellseffects_loadtried;
std::unordered_map<uint32, DBbotspells_Struct> Bot_Spells_Cache;
std::unordered_set<uint32> Bot_Spells_LoadTried;
std::unordered_map<uint32, DBbotspells_Struct> bot_spells_cache;
std::unordered_set<uint32> bot_spells_loadtried;
};
extern ZoneDatabase database;