[Bots] Add Data Bucket support to Bot Spell Entries. (#2505)

* [Bots] Add Data Bucket support to Bot Spell Entries.

* Cleanup Formatting and Functions

* Consolidated "CheckDataBucket" Functions

* Remove unneeded CastToClient

* Add choice to format data buckets as either "character-id" or "bot-id" to Bot spells

* Fix Formatting

* Clean up.

* Update npc.h

* Fix Bot Casting issues

* Formatting

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Kinglykrab <89047260+Kinglykrab@users.noreply.github.com>
This commit is contained in:
Aeadoin 2022-11-06 17:06:01 -05:00 committed by GitHub
parent de63eaa4b2
commit 7e7358e9b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 623 additions and 367 deletions

View File

@ -567,7 +567,7 @@ enum ReloadWorld : uint8 {
ForceRepop ForceRepop
}; };
enum MerchantBucketComparison : uint8 { enum BucketComparison : uint8 {
BucketEqualTo = 0, BucketEqualTo = 0,
BucketNotEqualTo, BucketNotEqualTo,
BucketGreaterThanOrEqualTo, BucketGreaterThanOrEqualTo,

View File

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

View File

@ -28,6 +28,7 @@
9027|2020_03_30_bots_view_update.sql|SELECT * FROM db_version WHERE bots_version >= 9027|empty| 9027|2020_03_30_bots_view_update.sql|SELECT * FROM db_version WHERE bots_version >= 9027|empty|
9028|2021_06_04_bot_create_combinations.sql|SHOW TABLES LIKE 'bot_create_combinations'|empty| 9028|2021_06_04_bot_create_combinations.sql|SHOW TABLES LIKE 'bot_create_combinations'|empty|
9029|2022_06_21_bot_groups_auto_spawn.sql|SHOW COLUMNS from `bot_groups` LIKE 'auto_spawm'|empty| 9029|2022_06_21_bot_groups_auto_spawn.sql|SHOW COLUMNS from `bot_groups` LIKE 'auto_spawm'|empty|
9030|2022_10_27_bot_data_buckets.sql|SHOW COLUMNS FROM `bot_spells_entries` LIKE 'bucket_name'|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,4 @@
ALTER TABLE `bot_spells_entries`
ADD COLUMN `bucket_name` varchar(100) NOT NULL DEFAULT '' AFTER `max_hp`,
ADD COLUMN `bucket_value` varchar(100) NOT NULL DEFAULT '' AFTER `bucket_name`,
ADD COLUMN `bucket_comparison` tinyint UNSIGNED NULL DEFAULT 0 AFTER `bucket_value`;

View File

@ -2188,7 +2188,6 @@ void Bot::AI_Bot_Start(uint32 iMoveDelay) {
} }
if (NPCTypedata) { if (NPCTypedata) {
AI_AddBotSpells(NPCTypedata->npc_spells_id);
ProcessSpecialAbilities(NPCTypedata->special_abilities); ProcessSpecialAbilities(NPCTypedata->special_abilities);
AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id); AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id);
} }
@ -9235,6 +9234,8 @@ void Bot::CalcBotStats(bool showtext) {
CalcBonuses(); CalcBonuses();
GetBotOwnerDataBuckets();
GetBotDataBuckets();
AI_AddBotSpells(GetBotSpellID()); AI_AddBotSpells(GetBotSpellID());
if(showtext) { if(showtext) {
@ -10322,6 +10323,80 @@ void Bot::SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leade
); );
} }
bool Bot::GetBotOwnerDataBuckets()
{
auto bot_owner = GetBotOwner();
if (!bot_owner) {
return false;
}
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()) {
return false;
}
for (auto row : results) {
bot_data_buckets.insert(std::pair<std::string,std::string>(row[0], row[1]));
}
return true;
}
bool Bot::GetBotDataBuckets()
{
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()) {
return false;
}
for (auto row : results) {
bot_data_buckets.insert(std::pair<std::string,std::string>(row[0], row[1]));
}
return true;
}
bool Bot::CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison)
{
if (!bucket_name.empty() && !bucket_value.empty()) {
auto full_name = fmt::format(
"{}-{}",
GetBucketKey(),
bucket_name
);
auto player_value = bot_data_buckets[full_name];
if (player_value.empty() && GetBotOwner()) {
full_name = fmt::format(
"{}-{}",
GetBotOwner()->GetBucketKey(),
bucket_name
);
player_value = bot_data_buckets[full_name];
if (player_value.empty()) {
return false;
}
}
if (zone->CheckDataBucket(bucket_comparison, bucket_value, player_value)) {
return true;
}
}
return false;
}
uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 };
#endif #endif

View File

@ -308,7 +308,19 @@ public:
void DoEnduranceUpkeep(); //does the endurance upkeep void DoEnduranceUpkeep(); //does the endurance upkeep
bool AI_AddBotSpells(uint32 iDBSpellsID); bool AI_AddBotSpells(uint32 iDBSpellsID);
void AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp); void AddSpellToBotList(
int16 iPriority,
uint16 iSpellID,
uint32 iType,
int16 iManaCost,
int32 iRecastDelay,
int16 iResistAdjust,
int8 min_hp,
int8 max_hp,
std::string bucket_name,
std::string bucket_value,
uint8 bucket_comparison
);
void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot);
// AI Methods // AI Methods
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
@ -349,6 +361,10 @@ public:
virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot); virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot);
virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0);
bool GetBotOwnerDataBuckets();
bool GetBotDataBuckets();
bool CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison);
// Bot Equipment & Inventory Class Methods // Bot Equipment & Inventory Class Methods
void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true); void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true);
void EquipBot(std::string* error_message); void EquipBot(std::string* error_message);
@ -692,6 +708,7 @@ private:
eStandingPetOrder m_previous_pet_order; eStandingPetOrder m_previous_pet_order;
BotCastingRoles m_CastingRoles; BotCastingRoles m_CastingRoles;
std::map<std::string,std::string> bot_data_buckets;
std::shared_ptr<HealRotation> m_member_of_heal_rotation; std::shared_ptr<HealRotation> m_member_of_heal_rotation;

View File

@ -804,8 +804,9 @@ bool BotDatabase::SaveBuffs(Bot* bot_inst)
return false; return false;
for (int buff_index = 0; buff_index < BUFF_COUNT; ++buff_index) { for (int buff_index = 0; buff_index < BUFF_COUNT; ++buff_index) {
if (bot_buffs[buff_index].spellid <= 0 || bot_buffs[buff_index].spellid == SPELL_UNKNOWN) if (!IsValidSpell(bot_buffs[buff_index].spellid)) {
continue; continue;
}
query = StringFormat( query = StringFormat(
"INSERT INTO `bot_buffs` (" "INSERT INTO `bot_buffs` ("

View File

@ -19,6 +19,7 @@
#ifdef BOTS #ifdef BOTS
#include "bot.h" #include "bot.h"
#include "../common/data_verification.h"
#include "../common/strings.h" #include "../common/strings.h"
#if EQDEBUG >= 12 #if EQDEBUG >= 12
@ -1604,11 +1605,16 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) {
std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) { std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) {
std::list<BotSpell> result; std::list<BotSpell> result;
auto bot_owner = botCaster->GetBotOwner();
if (!bot_owner) {
return result;
}
if (botCaster && botCaster->AI_HasSpells()) { if (botCaster && botCaster->AI_HasSpells()) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1631,11 +1637,16 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff
std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) { std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) {
std::list<BotSpell> result; std::list<BotSpell> result;
auto bot_owner = botCaster->GetBotOwner();
if (!bot_owner) {
return result;
}
if (botCaster && botCaster->AI_HasSpells()) { if (botCaster && botCaster->AI_HasSpells()) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1660,11 +1671,16 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster,
std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) {
std::list<BotSpell> result; std::list<BotSpell> result;
auto bot_owner = botCaster->GetBotOwner();
if (!bot_owner) {
return result;
}
if (botCaster && botCaster->AI_HasSpells()) { if (botCaster && botCaster->AI_HasSpells()) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1691,7 +1707,7 @@ std::list<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1708,8 +1724,13 @@ std::list<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa
} }
} }
if (result.size() > 1) if (result.size() > 1) {
result.sort([](BotSpell_wPriority& l, BotSpell_wPriority& r) { return l.Priority < r.Priority; }); result.sort(
[](BotSpell_wPriority& l, BotSpell_wPriority& r) {
return l.Priority < r.Priority;
}
);
}
} }
return result; return result;
@ -1726,7 +1747,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1784,15 +1805,18 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if (IsHealOverTimeSpell(botSpellListItr->SpellId)) { if (IsHealOverTimeSpell(botSpellListItr->SpellId)) {
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
} }
if(botSpellList[i].spellid == botSpellListItr->SpellId && (botSpellList[i].type & SpellType_Heal) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
botSpellList[i].spellid == botSpellListItr->SpellId &&
(botSpellList[i].type & SpellType_Heal) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
@ -1818,7 +1842,7 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -1828,7 +1852,6 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) {
result.SpellId = botSpellList[i].spellid; result.SpellId = botSpellList[i].spellid;
result.SpellIndex = i; result.SpellIndex = i;
result.ManaCost = botSpellList[i].manacost; result.ManaCost = botSpellList[i].manacost;
break; break;
} }
} }
@ -1853,7 +1876,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
break; break;
} }
} }
@ -1874,11 +1896,16 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if((IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) || IsFastHealSpell(botSpellListItr->SpellId)) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
(
IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) ||
IsFastHealSpell(botSpellListItr->SpellId)
) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
break; break;
} }
} }
@ -1899,11 +1926,13 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if(IsRegularGroupHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
IsRegularGroupHealSpell(botSpellListItr->SpellId) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
break; break;
} }
} }
@ -1928,13 +1957,17 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) {
if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) { if (IsGroupHealOverTimeSpell(botSpellListItr->SpellId)) {
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
} }
if(botSpellList[i].spellid == botSpellListItr->SpellId && (botSpellList[i].type & SpellType_Heal) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
botSpellList[i].spellid == botSpellListItr->SpellId &&
(botSpellList[i].type & SpellType_Heal) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
@ -1961,11 +1994,13 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) {
for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if(IsGroupCompleteHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if(
IsGroupCompleteHealSpell(botSpellListItr->SpellId) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
break; break;
} }
} }
@ -1986,7 +2021,10 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if(IsMezSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
IsMezSpell(botSpellListItr->SpellId) &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
@ -2011,11 +2049,14 @@ BotSpell Bot::GetBestBotSpellForMagicBasedSlow(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if (IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
IsSlowSpell(botSpellListItr->SpellId) &&
spells[botSpellListItr->SpellId].resist_type == RESIST_MAGIC &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
break; break;
} }
} }
@ -2036,7 +2077,11 @@ BotSpell Bot::GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster) {
for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { for (std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order // Assuming all the spells have been loaded into this list by level and in descending order
if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { if (
IsSlowSpell(botSpellListItr->SpellId) &&
spells[botSpellListItr->SpellId].resist_type == RESIST_DISEASE &&
CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)
) {
result.SpellId = botSpellListItr->SpellId; result.SpellId = botSpellListItr->SpellId;
result.SpellIndex = botSpellListItr->SpellIndex; result.SpellIndex = botSpellListItr->SpellIndex;
result.ManaCost = botSpellListItr->ManaCost; result.ManaCost = botSpellListItr->ManaCost;
@ -2067,7 +2112,9 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell) {
if (g) { if (g) {
for (int counter = 0; counter < g->GroupCount(); counter++) { for (int counter = 0; counter < g->GroupCount(); counter++) {
if(npc->IsOnHatelist(g->members[counter]) && g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) { if (
npc->IsOnHatelist(g->members[counter]) &&
g->members[counter]->GetTarget() != npc && g->members[counter]->IsEngaged()) {
result = npc; result = npc;
break; break;
} }
@ -2335,7 +2382,7 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -2382,7 +2429,7 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) {
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells; std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) { for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(botSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -2690,43 +2737,67 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
// ok, this function should load the list, and the parent list then shove them into the struct and sort // 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 = iDBSpellsID;
AIBot_spells.clear(); AIBot_spells.clear();
if (iDBSpellsID == 0) { if (!iDBSpellsID) {
AIautocastspell_timer->Disable(); AIautocastspell_timer->Disable();
return false; return false;
} }
DBbotspells_Struct* spell_list = content_db.GetBotSpells(iDBSpellsID);
auto* spell_list = content_db.GetBotSpells(iDBSpellsID);
if (!spell_list) { if (!spell_list) {
AIautocastspell_timer->Disable(); AIautocastspell_timer->Disable();
return false; return false;
} }
DBbotspells_Struct* parentlist = content_db.GetBotSpells(spell_list->parent_list);
std::string debug_msg = StringFormat("Loading NPCSpells onto %s: dbspellsid=%u, level=%u", GetName(), iDBSpellsID, GetLevel()); auto* parentlist = content_db.GetBotSpells(spell_list->parent_list);
auto debug_msg = fmt::format(
"Loading NPCSpells onto {}: dbspellsid={}, level={}",
GetName(),
iDBSpellsID,
GetLevel()
);
if (spell_list) { if (spell_list) {
debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->entries.size(), spell_list->parent_list)); debug_msg.append(
fmt::format(
" (found, {}), parentlist={}",
spell_list->entries.size(),
spell_list->parent_list
)
);
if (spell_list->parent_list) { if (spell_list->parent_list) {
if (parentlist) if (parentlist) {
debug_msg.append(StringFormat(" (found, %u)", parentlist->entries.size())); debug_msg.append(
else fmt::format(
" (found, {})",
parentlist->entries.size()
)
);
} else {
debug_msg.append(" (not found)"); debug_msg.append(" (not found)");
} }
} }
else { } else {
debug_msg.append(" (not found)"); debug_msg.append(" (not found)");
} }
LogAIDetail("[{}]", debug_msg.c_str());
LogAIDetail("[{}]", debug_msg);
if (parentlist) { if (parentlist) {
for (const auto &iter : parentlist->entries) { for (const auto &iter : parentlist->entries) {
LogAI("([{}]) [{}]", iter.spellid, spells[iter.spellid].name); LogAI("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
} }
} }
LogAIModerate("fin (parent list)"); LogAIModerate("fin (parent list)");
if (spell_list) { if (spell_list) {
for (const auto &iter : spell_list->entries) { for (const auto &iter : spell_list->entries) {
LogAIDetail("([{}]) [{}]", iter.spellid, spells[iter.spellid].name); LogAIDetail("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
} }
} }
LogAIModerate("fin (spell list)"); LogAIModerate("fin (spell list)");
uint16 attack_proc_spell = -1; uint16 attack_proc_spell = -1;
@ -2768,14 +2839,21 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
_idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max; _idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max;
_idle_beneficial_chance = parentlist->idle_beneficial_chance; _idle_beneficial_chance = parentlist->idle_beneficial_chance;
for (auto &e : parentlist->entries) { for (auto &e : parentlist->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) { if (
if (!IsSpellInBotList(spell_list, e.spellid)) EQ::ValueWithin(GetLevel(), e.minlevel, e.maxlevel) &&
{ e.spellid &&
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp); !IsSpellInBotList(spell_list, e.spellid)
} ) {
if (!e.bucket_name.empty() && !e.bucket_value.empty()) {
if (!CheckDataBucket(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);
} }
} }
} }
if (spell_list->attack_proc >= 0) { if (spell_list->attack_proc >= 0) {
attack_proc_spell = spell_list->attack_proc; attack_proc_spell = spell_list->attack_proc;
proc_chance = spell_list->proc_chance; proc_chance = spell_list->proc_chance;
@ -2792,10 +2870,20 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
} }
//If any casting variables are defined in the current list, ignore those in the parent list. //If any casting variables are defined in the current list, ignore those in the parent list.
if (spell_list->fail_recast || spell_list->engaged_no_sp_recast_min || spell_list->engaged_no_sp_recast_max if (
|| spell_list->engaged_beneficial_self_chance || spell_list->engaged_beneficial_other_chance || spell_list->engaged_detrimental_chance spell_list->fail_recast ||
|| spell_list->pursue_no_sp_recast_min || spell_list->pursue_no_sp_recast_max || spell_list->pursue_detrimental_chance spell_list->engaged_no_sp_recast_min ||
|| spell_list->idle_no_sp_recast_min || spell_list->idle_no_sp_recast_max || spell_list->idle_beneficial_chance) { spell_list->engaged_no_sp_recast_max ||
spell_list->engaged_beneficial_self_chance ||
spell_list->engaged_beneficial_other_chance ||
spell_list->engaged_detrimental_chance ||
spell_list->pursue_no_sp_recast_min ||
spell_list->pursue_no_sp_recast_max ||
spell_list->pursue_detrimental_chance ||
spell_list->idle_no_sp_recast_min ||
spell_list->idle_no_sp_recast_max ||
spell_list->idle_beneficial_chance
) {
_fail_recast = spell_list->fail_recast; _fail_recast = spell_list->fail_recast;
_engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min; _engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min;
_engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max; _engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max;
@ -2811,8 +2899,13 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
} }
for (auto &e : spell_list->entries) { for (auto &e : spell_list->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) { if (EQ::ValueWithin(GetLevel(), e.minlevel, e.maxlevel) && e.spellid) {
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp); if (!e.bucket_name.empty() && !e.bucket_value.empty()) {
if (!CheckDataBucket(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);
} }
} }
@ -2860,14 +2953,20 @@ bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
} }
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) { bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) {
auto it = std::find_if(spell_list->entries.begin(), spell_list->entries.end(), auto it = std::find_if(
[iSpellID](const DBbotspells_entries_Struct &a) { return a.spellid == iSpellID; }); spell_list->entries.begin(),
spell_list->entries.end(),
[iSpellID](const DBbotspells_entries_Struct &a) {
return a.spellid == iSpellID;
}
);
return it != spell_list->entries.end(); return it != spell_list->entries.end();
} }
DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID) DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
{ {
if (iDBSpellsID == 0) { if (!iDBSpellsID) {
return nullptr; return nullptr;
} }
@ -2880,83 +2979,87 @@ DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already
Bot_Spells_LoadTried.insert(iDBSpellsID); Bot_Spells_LoadTried.insert(iDBSpellsID);
std::string query = StringFormat("SELECT id, parent_list, attack_proc, proc_chance, " auto query = fmt::format(
"SELECT id, parent_list, attack_proc, proc_chance, "
"range_proc, rproc_chance, defensive_proc, dproc_chance, " "range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, " "fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, " "engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, " "pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_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=%d", "idle_b_chance FROM npc_spells WHERE id = {}",
iDBSpellsID); iDBSpellsID
auto results = QueryDatabase(query); );
if (!results.Success()) {
return nullptr;
}
if (results.RowCount() != 1) { auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return nullptr; return nullptr;
} }
auto row = results.begin(); auto row = results.begin();
DBbotspells_Struct spell_set; DBbotspells_Struct spell_set;
spell_set.parent_list = atoi(row[1]); spell_set.parent_list = std::stoul(row[1]);
spell_set.attack_proc = atoi(row[2]); spell_set.attack_proc = static_cast<uint16>(std::stoul(row[2]));
spell_set.proc_chance = atoi(row[3]); spell_set.proc_chance = static_cast<uint8>(std::stoul(row[3]));
spell_set.range_proc = atoi(row[4]); spell_set.range_proc = static_cast<uint16>(std::stoul(row[4]));
spell_set.rproc_chance = atoi(row[5]); spell_set.rproc_chance = static_cast<int16>(std::stoi(row[5]));
spell_set.defensive_proc = atoi(row[6]); spell_set.defensive_proc = static_cast<uint16>(std::stoul(row[6]));
spell_set.dproc_chance = atoi(row[7]); spell_set.dproc_chance = static_cast<int16>(std::stoi(row[7]));
spell_set.fail_recast = atoi(row[8]); spell_set.fail_recast = std::stoul(row[8]);
spell_set.engaged_no_sp_recast_min = atoi(row[9]); spell_set.engaged_no_sp_recast_min = std::stoul(row[9]);
spell_set.engaged_no_sp_recast_max = atoi(row[10]); spell_set.engaged_no_sp_recast_max = std::stoul(row[10]);
spell_set.engaged_beneficial_self_chance = atoi(row[11]); spell_set.engaged_beneficial_self_chance = static_cast<uint8>(std::stoul(row[11]));
spell_set.engaged_beneficial_other_chance = atoi(row[12]); spell_set.engaged_beneficial_other_chance = static_cast<uint8>(std::stoul(row[12]));
spell_set.engaged_detrimental_chance = atoi(row[13]); spell_set.engaged_detrimental_chance = static_cast<uint8>(std::stoul(row[13]));
spell_set.pursue_no_sp_recast_min = atoi(row[14]); spell_set.pursue_no_sp_recast_min = std::stoul(row[14]);
spell_set.pursue_no_sp_recast_max = atoi(row[15]); spell_set.pursue_no_sp_recast_max = std::stoul(row[15]);
spell_set.pursue_detrimental_chance = atoi(row[16]); spell_set.pursue_detrimental_chance = static_cast<uint8>(std::stoul(row[16]));
spell_set.idle_no_sp_recast_min = atoi(row[17]); spell_set.idle_no_sp_recast_min = std::stoul(row[17]);
spell_set.idle_no_sp_recast_max = atoi(row[18]); spell_set.idle_no_sp_recast_max = std::stoul(row[18]);
spell_set.idle_beneficial_chance = atoi(row[19]); spell_set.idle_beneficial_chance = static_cast<uint8>(std::stoul(row[19]));
// pulling fixed values from an auto-increment field is dangerous... // pulling fixed values from an auto-increment field is dangerous...
query = StringFormat( query = fmt::format(
"SELECT spellid, type, minlevel, maxlevel, " "SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust " "manacost, recast_delay, priority, min_hp, max_hp, resist_adjust, "
"bucket_name, bucket_value, bucket_comparison "
"FROM bot_spells_entries " "FROM bot_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel", "WHERE npc_spells_id = {} ORDER BY minlevel",
iDBSpellsID); iDBSpellsID
);
results = QueryDatabase(query); results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
if (!results.Success()) {
return nullptr; return nullptr;
} }
int entryIndex = 0; for (auto row : results) {
for (row = results.begin(); row != results.end(); ++row, ++entryIndex) {
DBbotspells_entries_Struct entry; DBbotspells_entries_Struct entry;
int spell_id = atoi(row[0]); auto spell_id = std::stoi(row[0]);
entry.spellid = spell_id; entry.spellid = spell_id;
entry.type = atoul(row[1]); entry.type = std::stoul(row[1]);
entry.minlevel = atoi(row[2]); entry.minlevel = static_cast<uint8>(std::stoul(row[2]));
entry.maxlevel = atoi(row[3]); entry.maxlevel = static_cast<uint8>(std::stoul(row[3]));
entry.manacost = atoi(row[4]); entry.manacost = static_cast<int16>(std::stoi(row[4]));
entry.recast_delay = atoi(row[5]); entry.recast_delay = std::stoi(row[5]);
entry.priority = atoi(row[6]); entry.priority = static_cast<int16>(std::stoi(row[6]));
entry.min_hp = atoi(row[7]); entry.min_hp = static_cast<int8>(std::stoi(row[7]));
entry.max_hp = atoi(row[8]); 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]));
// some spell types don't make much since to be priority 0, so fix that // some spell types don't make much since to be priority 0, so fix that
if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0) {
entry.priority = 1; entry.priority = 1;
}
if (row[9]) { if (row[9]) {
entry.resist_adjust = atoi(row[9]); entry.resist_adjust = static_cast<int16>(std::stoi(row[9]));
} } else if (IsValidSpell(spell_id)) {
else if (IsValidSpell(spell_id)) {
entry.resist_adjust = spells[spell_id].resist_difficulty; entry.resist_adjust = spells[spell_id].resist_difficulty;
} }
spell_set.entries.push_back(entry); spell_set.entries.push_back(entry);
} }
@ -2969,10 +3072,19 @@ DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
} }
// adds a spell to the list, taking into account priority and resorting list as needed. // adds a spell to the list, taking into account priority and resorting list as needed.
void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, void Bot::AddSpellToBotList(
int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp) int16 iPriority,
{ uint16 iSpellID,
uint32 iType,
int16 iManaCost,
int32 iRecastDelay,
int16 iResistAdjust,
int8 min_hp,
int8 max_hp,
std::string bucket_name,
std::string bucket_value,
uint8 bucket_comparison
) {
if(!IsValidSpell(iSpellID)) { if(!IsValidSpell(iSpellID)) {
return; return;
} }
@ -2989,6 +3101,9 @@ void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType,
t.resist_adjust = iResistAdjust; t.resist_adjust = iResistAdjust;
t.min_hp = min_hp; t.min_hp = min_hp;
t.max_hp = max_hp; t.max_hp = max_hp;
t.bucket_name = bucket_name;
t.bucket_value = bucket_value;
t.bucket_comparison = bucket_comparison;
AIBot_spells.push_back(t); AIBot_spells.push_back(t);

View File

@ -11607,149 +11607,6 @@ void Client::SendReloadCommandMessages() {
SendChatLineBreak(); SendChatLineBreak();
} }
bool Client::CheckMerchantDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value)
{
std::vector<std::string> bucket_checks;
bool found = false;
bool passes = false;
switch (bucket_comparison) {
case MerchantBucketComparison::BucketEqualTo:
{
if (player_value != bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketNotEqualTo:
{
if (player_value == bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketGreaterThanOrEqualTo:
{
if (player_value < bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketLesserThanOrEqualTo:
{
if (player_value > bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketGreaterThan:
{
if (player_value <= bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketLesserThan:
{
if (player_value >= bucket_value) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketIsAny:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
for (const auto &bucket : bucket_checks) {
if (player_value == bucket) {
found = true;
break;
}
}
if (!found) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketIsNotAny:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
for (const auto &bucket : bucket_checks) {
if (player_value == bucket) {
found = true;
break;
}
}
if (found) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketIsBetween:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (
std::stoll(player_value) < std::stoll(bucket_checks[0]) ||
std::stoll(player_value) > std::stoll(bucket_checks[1])
) {
break;
}
passes = true;
break;
}
case MerchantBucketComparison::BucketIsNotBetween:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (
std::stoll(player_value) >= std::stoll(bucket_checks[0]) &&
std::stoll(player_value) <= std::stoll(bucket_checks[1])
) {
break;
}
passes = true;
break;
}
}
return passes;
}
std::map<std::string,std::string> Client::GetMerchantDataBuckets() std::map<std::string,std::string> Client::GetMerchantDataBuckets()
{ {
std::map<std::string,std::string> merchant_data_buckets; std::map<std::string,std::string> merchant_data_buckets;

View File

@ -1633,7 +1633,6 @@ public:
Timer m_list_task_timers_rate_limit = {}; Timer m_list_task_timers_rate_limit = {};
std::map<std::string,std::string> GetMerchantDataBuckets(); std::map<std::string,std::string> GetMerchantDataBuckets();
bool CheckMerchantDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value);
protected: protected:
friend class Mob; friend class Mob;

View File

@ -855,7 +855,7 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
continue; continue;
} }
if (!CheckMerchantDataBucket(ml.bucket_comparison, bucket_value, player_value)) { if (!zone->CheckDataBucket(ml.bucket_comparison, bucket_value, player_value)) {
continue; continue;
} }
} }

View File

@ -2904,7 +2904,7 @@ std::list<MercSpell> Merc::GetMercSpellsBySpellType(Merc* caster, uint32 spellTy
std::vector<MercSpell> mercSpellList = caster->GetMercSpells(); std::vector<MercSpell> mercSpellList = caster->GetMercSpells();
for (int i = mercSpellList.size() - 1; i >= 0; i--) { for (int i = mercSpellList.size() - 1; i >= 0; i--) {
if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(mercSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -2941,7 +2941,7 @@ MercSpell Merc::GetFirstMercSpellBySpellType(Merc* caster, uint32 spellType) {
std::vector<MercSpell> mercSpellList = caster->GetMercSpells(); std::vector<MercSpell> mercSpellList = caster->GetMercSpells();
for (int i = mercSpellList.size() - 1; i >= 0; i--) { for (int i = mercSpellList.size() - 1; i >= 0; i--) {
if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(mercSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -2979,7 +2979,7 @@ MercSpell Merc::GetMercSpellBySpellID(Merc* caster, uint16 spellid) {
std::vector<MercSpell> mercSpellList = caster->GetMercSpells(); std::vector<MercSpell> mercSpellList = caster->GetMercSpells();
for (int i = mercSpellList.size() - 1; i >= 0; i--) { for (int i = mercSpellList.size() - 1; i >= 0; i--) {
if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(mercSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -3009,7 +3009,7 @@ std::list<MercSpell> Merc::GetMercSpellsForSpellEffect(Merc* caster, int spellEf
std::vector<MercSpell> mercSpellList = caster->GetMercSpells(); std::vector<MercSpell> mercSpellList = caster->GetMercSpells();
for (int i = mercSpellList.size() - 1; i >= 0; i--) { for (int i = mercSpellList.size() - 1; i >= 0; i--) {
if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(mercSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;
@ -3039,7 +3039,7 @@ std::list<MercSpell> Merc::GetMercSpellsForSpellEffectAndTargetType(Merc* caster
std::vector<MercSpell> mercSpellList = caster->GetMercSpells(); std::vector<MercSpell> mercSpellList = caster->GetMercSpells();
for (int i = mercSpellList.size() - 1; i >= 0; i--) { for (int i = mercSpellList.size() - 1; i >= 0; i--) {
if (mercSpellList[i].spellid <= 0 || mercSpellList[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(mercSpellList[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
continue; continue;

View File

@ -6867,6 +6867,10 @@ std::string Mob::GetBucketKey() {
return fmt::format("character-{}", CastToClient()->CharacterID()); return fmt::format("character-{}", CastToClient()->CharacterID());
} else if (IsNPC()) { } else if (IsNPC()) {
return fmt::format("npc-{}", GetNPCTypeID()); return fmt::format("npc-{}", GetNPCTypeID());
#ifdef BOTS
} else if (IsBot()) {
return fmt::format("bot-{}", CastToBot()->GetBotID());
#endif
} }
return std::string(); return std::string();
} }

View File

@ -84,7 +84,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates
float manaR = GetManaRatio(); float manaR = GetManaRatio();
for (int i = static_cast<int>(AIspells.size()) - 1; i >= 0; i--) { for (int i = static_cast<int>(AIspells.size()) - 1; i >= 0; i--) {
if (AIspells[i].spellid <= 0 || AIspells[i].spellid >= SPDAT_RECORDS) { if (!IsValidSpell(AIspells[i].spellid)) {
// this is both to quit early to save cpu and to avoid casting bad spells // this is both to quit early to save cpu and to avoid casting bad spells
// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here // Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
//return false; //return false;

View File

@ -69,6 +69,9 @@ struct BotSpells_Struct {
int16 resist_adjust; int16 resist_adjust;
int16 min_hp; // >0 won't cast if HP is below int16 min_hp; // >0 won't cast if HP is below
int16 max_hp; // >0 won't cast if HP is above int16 max_hp; // >0 won't cast if HP is above
std::string bucket_name;
std::string bucket_value;
uint8 bucket_comparison;
}; };
struct AISpellsEffects_Struct { struct AISpellsEffects_Struct {

175
zone/zone.cpp Executable file → Normal file
View File

@ -2976,3 +2976,178 @@ std::string Zone::GetAAName(int aa_id)
return std::string(); return std::string();
} }
bool Zone::CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value)
{
std::vector<std::string> bucket_checks;
bool found = false;
bool passes = false;
switch (bucket_comparison) {
case BucketComparison::BucketEqualTo:
{
if (player_value != bucket_value) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketNotEqualTo:
{
if (player_value == bucket_value) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketGreaterThanOrEqualTo:
{
if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) {
break;
}
if (std::stoll(player_value) < std::stoll(bucket_value)) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketLesserThanOrEqualTo:
{
if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) {
break;
}
if (std::stoll(player_value) > std::stoll(bucket_value)) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketGreaterThan:
{
if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) {
break;
}
if (std::stoll(player_value) <= std::stoll(bucket_value)) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketLesserThan:
{
if (!Strings::IsNumber(player_value) || !Strings::IsNumber(bucket_value)) {
break;
}
if (std::stoll(player_value) >= std::stoll(bucket_value)) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketIsAny:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (std::find(bucket_checks.begin(), bucket_checks.end(), player_value) != bucket_checks.end()) {
found = true;
}
if (!found) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketIsNotAny:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (std::find(bucket_checks.begin(), bucket_checks.end(), player_value) != bucket_checks.end()) {
found = true;
}
if (found) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketIsBetween:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (
!Strings::IsNumber(player_value) ||
!Strings::IsNumber(bucket_checks[0]) ||
!Strings::IsNumber(bucket_checks[1])
) {
break;
}
if (
!EQ::ValueWithin(
std::stoll(player_value),
std::stoll(bucket_checks[0]),
std::stoll(bucket_checks[1])
)
) {
break;
}
passes = true;
break;
}
case BucketComparison::BucketIsNotBetween:
{
bucket_checks = Strings::Split(bucket_value, "|");
if (bucket_checks.empty()) {
break;
}
if (
!Strings::IsNumber(player_value) ||
!Strings::IsNumber(bucket_checks[0]) ||
!Strings::IsNumber(bucket_checks[1])
) {
break;
}
if (
EQ::ValueWithin(
std::stoll(player_value),
std::stoll(bucket_checks[0]),
std::stoll(bucket_checks[1])
)
) {
break;
}
passes = true;
break;
}
}
return passes;
}

View File

@ -311,6 +311,8 @@ public:
bool IsQuestHotReloadQueued() const; bool IsQuestHotReloadQueued() const;
void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued); void SetQuestHotReloadQueued(bool in_quest_hot_reload_queued);
bool CheckDataBucket(uint8 bucket_comparison, std::string bucket_value, std::string player_value);
WaterMap *watermap; WaterMap *watermap;
ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f);
ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f);

View File

@ -119,6 +119,9 @@ struct DBbotspells_entries_Struct {
int16 resist_adjust; int16 resist_adjust;
int8 min_hp; int8 min_hp;
int8 max_hp; int8 max_hp;
std::string bucket_name;
std::string bucket_value;
uint8 bucket_comparison;
}; };
#pragma pack() #pragma pack()