diff --git a/common/repositories/base/base_raid_members_repository.h b/common/repositories/base/base_raid_members_repository.h index 07defda40..3a0d34dcb 100644 --- a/common/repositories/base/base_raid_members_repository.h +++ b/common/repositories/base/base_raid_members_repository.h @@ -16,11 +16,14 @@ #include "../../strings.h" #include + class BaseRaidMembersRepository { public: struct RaidMembers { + uint64_t id; int32_t raidid; int32_t charid; + int32_t bot_id; uint32_t groupid; int8_t _class; int8_t level; @@ -32,14 +35,16 @@ public: static std::string PrimaryKey() { - return std::string("charid"); + return std::string("id"); } static std::vector Columns() { return { + "id", "raidid", "charid", + "bot_id", "groupid", "_class", "level", @@ -53,8 +58,10 @@ public: static std::vector SelectColumns() { return { + "id", "raidid", "charid", + "bot_id", "groupid", "_class", "level", @@ -102,8 +109,10 @@ public: { RaidMembers e{}; + e.id = 0; e.raidid = 0; e.charid = 0; + e.bot_id = 0; e.groupid = 0; e._class = 0; e.level = 0; @@ -121,7 +130,7 @@ public: ) { for (auto &raid_members : raid_memberss) { - if (raid_members.charid == raid_members_id) { + if (raid_members.id == raid_members_id) { return raid_members; } } @@ -136,8 +145,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), raid_members_id ) ); @@ -146,15 +156,17 @@ public: if (results.RowCount() == 1) { RaidMembers e{}; - e.raidid = static_cast(atoi(row[0])); - e.charid = static_cast(atoi(row[1])); - e.groupid = static_cast(strtoul(row[2], nullptr, 10)); - e._class = static_cast(atoi(row[3])); - e.level = static_cast(atoi(row[4])); - e.name = row[5] ? row[5] : ""; - e.isgroupleader = static_cast(atoi(row[6])); - e.israidleader = static_cast(atoi(row[7])); - e.islooter = static_cast(atoi(row[8])); + e.id = strtoull(row[0], nullptr, 10); + e.raidid = static_cast(atoi(row[1])); + e.charid = static_cast(atoi(row[2])); + e.bot_id = static_cast(atoi(row[3])); + e.groupid = static_cast(strtoul(row[4], nullptr, 10)); + e._class = static_cast(atoi(row[5])); + e.level = static_cast(atoi(row[6])); + e.name = row[7] ? row[7] : ""; + e.isgroupleader = static_cast(atoi(row[8])); + e.israidleader = static_cast(atoi(row[9])); + e.islooter = static_cast(atoi(row[10])); return e; } @@ -188,15 +200,16 @@ public: auto columns = Columns(); - v.push_back(columns[0] + " = " + std::to_string(e.raidid)); - v.push_back(columns[1] + " = " + std::to_string(e.charid)); - v.push_back(columns[2] + " = " + std::to_string(e.groupid)); - v.push_back(columns[3] + " = " + std::to_string(e._class)); - v.push_back(columns[4] + " = " + std::to_string(e.level)); - v.push_back(columns[5] + " = '" + Strings::Escape(e.name) + "'"); - v.push_back(columns[6] + " = " + std::to_string(e.isgroupleader)); - v.push_back(columns[7] + " = " + std::to_string(e.israidleader)); - v.push_back(columns[8] + " = " + std::to_string(e.islooter)); + v.push_back(columns[1] + " = " + std::to_string(e.raidid)); + v.push_back(columns[2] + " = " + std::to_string(e.charid)); + v.push_back(columns[3] + " = " + std::to_string(e.bot_id)); + v.push_back(columns[4] + " = " + std::to_string(e.groupid)); + v.push_back(columns[5] + " = " + std::to_string(e._class)); + v.push_back(columns[6] + " = " + std::to_string(e.level)); + v.push_back(columns[7] + " = '" + Strings::Escape(e.name) + "'"); + v.push_back(columns[8] + " = " + std::to_string(e.isgroupleader)); + v.push_back(columns[9] + " = " + std::to_string(e.israidleader)); + v.push_back(columns[10] + " = " + std::to_string(e.islooter)); auto results = db.QueryDatabase( fmt::format( @@ -204,7 +217,7 @@ public: TableName(), Strings::Implode(", ", v), PrimaryKey(), - e.charid + e.id ) ); @@ -218,8 +231,10 @@ public: { std::vector v; + v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.raidid)); v.push_back(std::to_string(e.charid)); + v.push_back(std::to_string(e.bot_id)); v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e._class)); v.push_back(std::to_string(e.level)); @@ -237,7 +252,7 @@ public: ); if (results.Success()) { - e.charid = results.LastInsertedID(); + e.id = results.LastInsertedID(); return e; } @@ -256,8 +271,10 @@ public: for (auto &e: entries) { std::vector v; + v.push_back(std::to_string(e.id)); v.push_back(std::to_string(e.raidid)); v.push_back(std::to_string(e.charid)); + v.push_back(std::to_string(e.bot_id)); v.push_back(std::to_string(e.groupid)); v.push_back(std::to_string(e._class)); v.push_back(std::to_string(e.level)); @@ -298,15 +315,17 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { RaidMembers e{}; - e.raidid = static_cast(atoi(row[0])); - e.charid = static_cast(atoi(row[1])); - e.groupid = static_cast(strtoul(row[2], nullptr, 10)); - e._class = static_cast(atoi(row[3])); - e.level = static_cast(atoi(row[4])); - e.name = row[5] ? row[5] : ""; - e.isgroupleader = static_cast(atoi(row[6])); - e.israidleader = static_cast(atoi(row[7])); - e.islooter = static_cast(atoi(row[8])); + e.id = strtoull(row[0], nullptr, 10); + e.raidid = static_cast(atoi(row[1])); + e.charid = static_cast(atoi(row[2])); + e.bot_id = static_cast(atoi(row[3])); + e.groupid = static_cast(strtoul(row[4], nullptr, 10)); + e._class = static_cast(atoi(row[5])); + e.level = static_cast(atoi(row[6])); + e.name = row[7] ? row[7] : ""; + e.isgroupleader = static_cast(atoi(row[8])); + e.israidleader = static_cast(atoi(row[9])); + e.islooter = static_cast(atoi(row[10])); all_entries.push_back(e); } @@ -331,15 +350,17 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { RaidMembers e{}; - e.raidid = static_cast(atoi(row[0])); - e.charid = static_cast(atoi(row[1])); - e.groupid = static_cast(strtoul(row[2], nullptr, 10)); - e._class = static_cast(atoi(row[3])); - e.level = static_cast(atoi(row[4])); - e.name = row[5] ? row[5] : ""; - e.isgroupleader = static_cast(atoi(row[6])); - e.israidleader = static_cast(atoi(row[7])); - e.islooter = static_cast(atoi(row[8])); + e.id = strtoull(row[0], nullptr, 10); + e.raidid = static_cast(atoi(row[1])); + e.charid = static_cast(atoi(row[2])); + e.bot_id = static_cast(atoi(row[3])); + e.groupid = static_cast(strtoul(row[4], nullptr, 10)); + e._class = static_cast(atoi(row[5])); + e.level = static_cast(atoi(row[6])); + e.name = row[7] ? row[7] : ""; + e.isgroupleader = static_cast(atoi(row[8])); + e.israidleader = static_cast(atoi(row[9])); + e.islooter = static_cast(atoi(row[10])); all_entries.push_back(e); } diff --git a/common/seperator.h b/common/seperator.h index 7e81f8164..11ddfef26 100644 --- a/common/seperator.h +++ b/common/seperator.h @@ -99,8 +99,9 @@ public: } } ~Seperator() { - for (int i=0; i<=maxargnum; i++) + for (int i=0; i<=maxargnum; i++) { safe_delete_array(arg[i]); + } safe_delete_array(arg); safe_delete_array(argplus); safe_delete_array(msg); diff --git a/common/version.h b/common/version.h index 10a96ef1b..3941cce9d 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,8 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9224 +#define CURRENT_BINARY_DATABASE_VERSION 9225 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9038 #endif diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index ace611714..6b70625b9 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -478,6 +478,7 @@ 9222|2023_02_28_npc_scaling_zone_list_version_list.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'zone_id_list'|empty| 9223|2023_03_04_npc_scale_global_base_heroic_strikethrough.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'heroic_strikethrough'|empty| 9224|2023_03_08_npc_scale_global_base_avoidance.sql|SHOW COLUMNS FROM `npc_scale_global_base` LIKE 'hp_regen_per_second'|empty| +9225|2023_01_21_bots_raid_members.sql|SHOW COLUMNS FROM `raid_members` LIKE 'botid'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2023_01_21_bots_raid_members.sql b/utils/sql/git/required/2023_01_21_bots_raid_members.sql new file mode 100644 index 000000000..45cfbf2ec --- /dev/null +++ b/utils/sql/git/required/2023_01_21_bots_raid_members.sql @@ -0,0 +1,4 @@ +DROP INDEX IF EXISTS `PRIMARY` ON `raid_members`; +CREATE UNIQUE INDEX IF NOT EXISTS `UNIQUE` ON `raid_members`(`name`); +ALTER TABLE `raid_members` ADD COLUMN `bot_id` int(4) NOT NULL DEFAULT 0 AFTER `charid`; +ALTER TABLE `raid_members` ADD COLUMN `id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST; diff --git a/world/cliententry.cpp b/world/cliententry.cpp index 19ca12490..f4270c4b5 100644 --- a/world/cliententry.cpp +++ b/world/cliententry.cpp @@ -120,8 +120,9 @@ ClientListEntry::~ClientListEntry() Camp(); // updates zoneserver's numplayers client_list.RemoveCLEReferances(this); } - for (auto &elem : tell_queue) - safe_delete_array(elem); + for (auto& elem: tell_queue) { + safe_delete_array(elem) + }; tell_queue.clear(); } @@ -282,8 +283,9 @@ void ClientListEntry::ClearVars(bool iAll) pLFG = 0; gm = 0; pClientVersion = 0; - for (auto &elem : tell_queue) - safe_delete_array(elem); + for (auto& elem: tell_queue) { + safe_delete_array(elem) + }; tell_queue.clear(); } diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 00fbc8c8d..d5ad9a67e 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -11,6 +11,7 @@ SET(zone_sources beacon.cpp bonuses.cpp bot.cpp + bot_raid.cpp bot_command.cpp bot_database.cpp botspellsai.cpp diff --git a/zone/attack.cpp b/zone/attack.cpp index 94c775fdd..2f408e612 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3829,11 +3829,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons } //end `if there is some damage being done and theres anattacker person involved` - Mob *pet = GetPet(); // pets that have GHold will never automatically add NPCs // pets that have Hold and no Focus will add NPCs if they're engaged // pets that have Hold and Focus will not add NPCs if ( + Mob* pet = GetPet(); pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && @@ -3844,26 +3844,25 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && - !attacker->IsTrap() + !attacker->IsTrap() && + !pet->IsHeld() ) { - if (!pet->IsHeld()) { - LogAggro("Sending pet [{}] into battle due to attack", pet->GetName()); - if (IsClient()) { - // if pet was sitting his new mode is follow - // following after the battle (live verified) - if (pet->GetPetOrder() == SPO_Sit) { - pet->SetPetOrder(SPO_Follow); - } - - // fix GUI sit button to be unpressed and stop sitting regen - CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0); - pet->SetAppearance(eaStanding); + LogAggro("Sending pet [{}] into battle due to attack", pet->GetName()); + if (IsClient()) { + // if pet was sitting his new mode is follow + // following after the battle (live verified) + if (pet->GetPetOrder() == SPO_Sit) { + pet->SetPetOrder(SPO_Follow); } - pet->AddToHateList(attacker, 1, 0, true, false, false, spell_id); - pet->SetTarget(attacker); - MessageString(Chat::NPCQuestSay, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName()); + // fix GUI sit button to be unpressed and stop sitting regen + CastToClient()->SetPetCommandState(PET_BUTTON_SIT, 0); + pet->SetAppearance(eaStanding); } + + pet->AddToHateList(attacker, 1, 0, true, false, false, spell_id); + pet->SetTarget(attacker); + MessageString(Chat::NPCQuestSay, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName()); } if (GetTempPetCount()) { @@ -3884,9 +3883,18 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons else { int64 origdmg = damage; damage = AffectMagicalDamage(damage, spell_id, iBuffTic, attacker); - if (origdmg != damage && attacker && attacker->IsClient()) { - if (attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide) - attacker->Message(Chat::Yellow, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg); + if ( + origdmg != damage && + attacker && + attacker->IsClient() && + attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide + ) { + attacker->Message( + Chat::Yellow, + "The Spellshield absorbed %d of %d points of damage", + origdmg - damage, + origdmg + ); } if (damage == 0 && attacker && origdmg != damage && IsClient()) { //Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes. @@ -4212,7 +4220,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons //Note: if players can become pets, they will not receive damage messages of their own //this was done to simplify the code here (since we can only effectively skip one mob on queue) eqFilterType filter; - Mob *skip = attacker; + Mob* skip = attacker; if (attacker && attacker->GetOwnerID()) { //attacker is a pet, let pet owners see their pet's damage Mob* owner = attacker->GetOwner(); @@ -4239,6 +4247,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons owner->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); } } + skip = owner; } else { @@ -4385,11 +4394,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons spells[spell_id].name /* Message4 */ ); } - } //end packet sending + } -} +} //end packet sending -void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id) +void Mob::HealDamage(uint64 amount, Mob* caster, uint16 spell_id) { int64 maxhp = GetMaxHP(); int64 curhp = GetHP(); @@ -4403,16 +4412,17 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id) if (acthealed > 100) { if (caster) { if (IsBuffSpell(spell_id)) { // hots - // message to caster - if (caster->IsClient() && caster == this) { - if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) + // message to caster + if ((caster->IsClient() && caster == this)) { + if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) { FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime, HOT_HEAL_SELF, itoa(acthealed), spells[spell_id].name); + } else FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime, YOU_HEALED, GetCleanName(), itoa(acthealed)); } - else if (caster->IsClient() && caster != this) { + else if ((caster->IsClient() && caster != this)) { if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) caster->FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime, HOT_HEAL_OTHER, GetCleanName(), itoa(acthealed), @@ -4421,6 +4431,7 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id) caster->FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime, YOU_HEAL, GetCleanName(), itoa(acthealed)); } + // message to target if (IsClient() && caster != this) { if (CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) @@ -4435,7 +4446,7 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id) else { // normal heals FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage, YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); - if (caster != this) + caster->FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage, YOU_HEAL, GetCleanName(), itoa(acthealed)); } @@ -4446,10 +4457,12 @@ void Mob::HealDamage(uint64 amount, Mob *caster, uint16 spell_id) } if (curhp < maxhp) { - if ((curhp + amount) > maxhp) + if ((curhp + amount) > maxhp) { curhp = maxhp; - else + } + else { curhp += amount; + } SetHP(curhp); SendHPUpdate(); diff --git a/zone/bot.cpp b/zone/bot.cpp index 6b9bf541c..28d64d3c3 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -18,6 +18,7 @@ #include "bot.h" #include "object.h" +#include "raids.h" #include "doors.h" #include "quest_parser_collection.h" #include "lua_parser.h" @@ -27,16 +28,17 @@ #include "../common/data_verification.h" extern volatile bool is_zone_loaded; +extern bool Critical; // This constructor is used during the bot create command Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm::vec4(), Ground, false), rest_timer(1), ping_timer(1) { GiveNPCTypeData(npcTypeData); - if(botOwner) { + if (botOwner) { SetBotOwner(botOwner); _botOwnerCharacterID = botOwner->CharacterID(); } else { - SetBotOwner(0); + SetBotOwner(nullptr); _botOwnerCharacterID = 0; } @@ -68,8 +70,8 @@ Bot::Bot(NPCType *npcTypeData, Client* botOwner) : NPC(npcTypeData, nullptr, glm RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - m_enforce_spell_settings = 0; - m_bot_archery_setting = 0; + m_enforce_spell_settings = false; + m_bot_archery_setting = false; m_expansion_bitmask = -1; m_bot_caster_range = 0; SetBotID(0); @@ -128,7 +130,7 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to GiveNPCTypeData(npcTypeData); _botOwnerCharacterID = botOwnerCharacterID; - if(_botOwnerCharacterID > 0) + if (_botOwnerCharacterID > 0) SetBotOwner(entity_list.GetClientByCharID(_botOwnerCharacterID)); auto bot_owner = GetBotOwner(); @@ -223,8 +225,8 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to std::string error_message; EquipBot(&error_message); - if(!error_message.empty()) { - if(bot_owner) + if (!error_message.empty()) { + if (bot_owner) bot_owner->Message(Chat::White, error_message.c_str()); error_message.clear(); } @@ -262,11 +264,8 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to const SPDat_Spell_Struct& spell = spells[buffs[j1].spellid]; - int NimbusEffect = GetNimbusEffect(buffs[j1].spellid); - if (NimbusEffect) { - if (!IsNimbusEffectActive(NimbusEffect)) { - SendSpellEffect(NimbusEffect, 500, 0, 1, 3000, true); - } + if (int NimbusEffect = GetNimbusEffect(buffs[j1].spellid); NimbusEffect && !IsNimbusEffectActive(NimbusEffect)) { + SendSpellEffect(NimbusEffect, 500, 0, 1, 3000, true); } for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { @@ -394,10 +393,10 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to hp_regen = CalcHPRegen(); mana_regen = CalcManaRegen(); end_regen = CalcEnduranceRegen(); - if(current_hp > max_hp) + if (current_hp > max_hp) current_hp = max_hp; - if(current_hp <= 0) { + if (current_hp <= 0) { if (RuleB(Spells, BuffsFadeOnDeath)) { BuffFadeNonPersistDeath(); } @@ -423,21 +422,31 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to } } - if(current_mana > max_mana) + if (current_mana > max_mana) { current_mana = max_mana; + } cur_end = max_end; + + // Safety Check to confirm we have a valid group + if (HasGroup() && !GetGroup()->IsGroupMember(GetBotOwner())) { + Bot::RemoveBotFromGroup(this, GetGroup()); + } + + // Safety Check to confirm we have a valid raid + if (HasRaid() && !GetRaid()->IsRaidMember(GetBotOwner()->CastToClient())) { + Bot::RemoveBotFromRaid(this); + } + } Bot::~Bot() { AI_Stop(); LeaveHealRotationMemberPool(); - if(HasGroup()) - Bot::RemoveBotFromGroup(this, GetGroup()); - - if(HasPet()) + if (HasPet()) { GetPet()->Depop(); + } entity_list.RemoveBot(GetID()); } @@ -451,14 +460,14 @@ void Bot::SetBotSpellID(uint32 newSpellID) { npc_spells_id = newSpellID; } -void Bot::SetSurname(std::string bot_surname) { +void Bot::SetSurname(std::string_view bot_surname) { _surname = bot_surname.substr(0, 31); if (spawned) { auto outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); - GMLastName_Struct* gmn = (GMLastName_Struct*)outapp->pBuffer; + auto gmn = (GMLastName_Struct*)outapp->pBuffer; strcpy(gmn->name, GetCleanName()); strcpy(gmn->gmname, GetCleanName()); @@ -473,14 +482,14 @@ void Bot::SetSurname(std::string bot_surname) { } } -void Bot::SetTitle(std::string bot_title) { +void Bot::SetTitle(std::string_view bot_title) { _title = bot_title.substr(0, 31); if (spawned) { auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - SetTitleReply_Struct* strs = (SetTitleReply_Struct*)outapp->pBuffer; + auto strs = (SetTitleReply_Struct*)outapp->pBuffer; strs->is_suffix = 0; strn0cpy(strs->title, _title.c_str(), sizeof(strs->title)); @@ -491,14 +500,14 @@ void Bot::SetTitle(std::string bot_title) { } } -void Bot::SetSuffix(std::string bot_suffix) { +void Bot::SetSuffix(std::string_view bot_suffix) { _suffix = bot_suffix.substr(0, 31); if (spawned) { auto outapp = new EQApplicationPacket(OP_SetTitleReply, sizeof(SetTitleReply_Struct)); - SetTitleReply_Struct* strs = (SetTitleReply_Struct*)outapp->pBuffer; + auto strs = (SetTitleReply_Struct*)outapp->pBuffer; strs->is_suffix = 1; strn0cpy(strs->title, _suffix.c_str(), sizeof(strs->title)); @@ -525,8 +534,8 @@ uint32 Bot::GetBotArcheryRange() { } void Bot::ChangeBotArcherWeapons(bool isArcher) { - if((GetClass()==WARRIOR) || (GetClass()==PALADIN) || (GetClass()==RANGER) || (GetClass()==SHADOWKNIGHT) || (GetClass()==ROGUE)) { - if(!isArcher) { + if ((GetClass()==WARRIOR) || (GetClass()==PALADIN) || (GetClass()==RANGER) || (GetClass()==SHADOWKNIGHT) || (GetClass()==ROGUE)) { + if (!isArcher) { BotAddEquipItem(EQ::invslot::slotPrimary, GetBotItemBySlot(EQ::invslot::slotPrimary)); BotAddEquipItem(EQ::invslot::slotSecondary, GetBotItemBySlot(EQ::invslot::slotSecondary)); SetAttackTimer(); @@ -545,7 +554,7 @@ void Bot::ChangeBotArcherWeapons(bool isArcher) { } void Bot::Sit() { - if(IsMoving()) { + if (IsMoving()) { moved = false; StopNavigation(); } @@ -559,7 +568,7 @@ void Bot::Stand() { bool Bot::IsSitting() const { bool result = false; - if(GetAppearance() == eaSitting && !IsMoving()) + if (GetAppearance() == eaSitting && !IsMoving()) result = true; return result; @@ -567,7 +576,7 @@ bool Bot::IsSitting() const { bool Bot::IsStanding() { bool result = false; - if(GetAppearance() == eaStanding) + if (GetAppearance() == eaStanding) result = true; return result; @@ -575,8 +584,8 @@ bool Bot::IsStanding() { NPCType *Bot::FillNPCTypeStruct( uint32 botSpellsID, - std::string botName, - std::string botLastName, + const std::string& botName, + const std::string& botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, @@ -625,19 +634,7 @@ NPCType *Bot::FillNPCTypeStruct( n->bodytype = 1; n->deity = EQ::deity::DeityAgnostic; n->level = botLevel; - //n->npc_id = 0; - //n->texture = 0; - //n->helmtexture = 0; - //n->herosforgemodel = 0; - //n->loottable_id = 0; n->npc_spells_id = botSpellsID; - //n->npc_spells_effects_id = 0; - //n->npc_faction_id = 0; - //n->merchanttype = 0; - //n->alt_currency_type = 0; - //n->adventure_template = 0; - //n->trap_template = 0; - //n->light = 0; n->AC = ac; n->Mana = mana; n->ATK = attack; @@ -654,7 +651,6 @@ NPCType *Bot::FillNPCTypeStruct( n->PR = pr; n->DR = dr; n->Corrup = corrup; - //n->PhR = 0; n->haircolor = hairColor; n->beardcolor = beardColor; n->eyecolor1 = eyeColor; @@ -665,63 +661,12 @@ NPCType *Bot::FillNPCTypeStruct( n->drakkin_heritage = drakkinHeritage; n->drakkin_tattoo = drakkinTattoo; n->drakkin_details = drakkinDetails; - //n->armor_tint = { 0 }; - //n->min_dmg = 0; - //n->max_dmg = 0; - //n->charm_ac = 0; - //n->charm_min_dmg = 0; - //n->charm_max_dmg = 0; - //n->charm_attack_delay = 0; - //n->charm_accuracy_rating = 0; - //n->charm_avoidance_rating = 0; - //n->charm_atk = 0; - //n->attack_count = 0; - //*n->special_abilities = { 0 }; - //n->d_melee_texture1 = 0; - //n->d_melee_texture2 = 0; - //*n->ammo_idfile = { 0 }; - //n->prim_melee_type = 0; - //n->sec_melee_type = 0; - //n->ranged_type = 0; n->hp_regen = 1; n->mana_regen = 1; - //n->aggroradius = 0; - //n->assistradius = 0; - //n->see_invis = 0; - //n->see_invis_undead = false; - //n->see_hide = false; - //n->see_improved_hide = false; - //n->qglobal = false; - //n->npc_aggro = false; - //n->spawn_limit = 0; - //n->mount_color = 0; - //n->attack_speed = 0.0f; - //n->attack_delay = 0; - //n->accuracy_rating = 0; - //n->avoidance_rating = 0; - //n->findable = false; n->trackable = true; - //n->slow_mitigation = 0; n->maxlevel = botLevel; - //n->scalerate = 0; - //n->private_corpse = false; - //n->unique_spawn_by_name = false; - //n->underwater = false; - //n->emoteid = 0; - //n->spellscale = 0.0f; - //n->healscale = 0.0f; - //n->no_target_hotkey = false; - //n->raid_target = false; - //n->armtexture = 0; - //n->bracertexture = 0; - //n->handtexture = 0; - //n->legtexture = 0; - //n->feettexture = 0; - //n->ignore_despawn = false; n->show_name = true; - //n->untargetable = false; n->skip_global_loot = true; - //n->rare_spawn = false; n->stuck_behavior = Ground; n->skip_auto_scale = true; @@ -729,8 +674,8 @@ NPCType *Bot::FillNPCTypeStruct( } NPCType *Bot::CreateDefaultNPCTypeStructForBot( - std::string botName, - std::string botLastName, + const std::string& botName, + const std::string& botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, @@ -741,8 +686,6 @@ NPCType *Bot::CreateDefaultNPCTypeStructForBot( strn0cpy(n->name, botName.c_str(), sizeof(n->name)); strn0cpy(n->lastname, botLastName.c_str(), sizeof(n->lastname)); - //n->current_hp = 0; - //n->max_hp = 0; n->size = 6.0f; n->runspeed = 0.7f; n->gender = gender; @@ -751,21 +694,7 @@ NPCType *Bot::CreateDefaultNPCTypeStructForBot( n->bodytype = 1; n->deity = EQ::deity::DeityAgnostic; n->level = botLevel; - //n->npc_id = 0; - //n->texture = 0; - //n->helmtexture = 0; - //n->herosforgemodel = 0; - //n->loottable_id = 0; - //n->npc_spells_id = 0; - //n->npc_spells_effects_id = 0; - //n->npc_faction_id = 0; - //n->merchanttype = 0; - //n->alt_currency_type = 0; - //n->adventure_template = 0; - //n->trap_template = 0; - //n->light = 0; n->AC = 12; - //n->Mana = 0; n->ATK = 75; n->STR = 75; n->STA = 75; @@ -780,74 +709,12 @@ NPCType *Bot::CreateDefaultNPCTypeStructForBot( n->PR = 15; n->DR = 15; n->Corrup = 15; - //n->PhR = 0; - //n->haircolor = 0; - //n->beardcolor = 0; - //n->eyecolor1 = 0; - //n->eyecolor2 = 0; - //n->hairstyle = 0; - //n->luclinface = 0; - //n->beard = 0; - //n->drakkin_heritage = 0; - //n->drakkin_tattoo = 0; - //n->drakkin_details = 0; - //n->armor_tint = { 0 }; - //n->min_dmg = 0; - //n->max_dmg = 0; - //n->charm_ac = 0; - //n->charm_min_dmg = 0; - //n->charm_max_dmg = 0; - //n->charm_attack_delay = 0; - //n->charm_accuracy_rating = 0; - //n->charm_avoidance_rating = 0; - //n->charm_atk = 0; - //n->attack_count = 0; - //*n->special_abilities = { 0 }; - //n->d_melee_texture1 = 0; - //n->d_melee_texture2 = 0; - //*n->ammo_idfile = { 0 }; - //n->prim_melee_type = 0; - //n->sec_melee_type = 0; - //n->ranged_type = 0; n->hp_regen = 1; n->mana_regen = 1; - //n->aggroradius = 0; - //n->assistradius = 0; - //n->see_invis = 0; - //n->see_invis_undead = false; - //n->see_hide = false; - //n->see_improved_hide = false; - //n->qglobal = false; - //n->npc_aggro = false; - //n->spawn_limit = 0; - //n->mount_color = 0; - //n->attack_speed = 0.0f; - //n->attack_delay = 0; - //n->accuracy_rating = 0; - //n->avoidance_rating = 0; - //n->findable = false; n->trackable = true; - //n->slow_mitigation = 0; n->maxlevel = botLevel; - //n->scalerate = 0; - //n->private_corpse = false; - //n->unique_spawn_by_name = false; - //n->underwater = false; - //n->emoteid = 0; - //n->spellscale = 0.0f; - //n->healscale = 0.0f; - //n->no_target_hotkey = false; - //n->raid_target = false; - //n->armtexture = 0; - //n->bracertexture = 0; - //n->handtexture = 0; - //n->legtexture = 0; - //n->feettexture = 0; - //n->ignore_despawn = false; n->show_name = true; - //n->untargetable = false; n->skip_global_loot = true; - //n->rare_spawn = false; n->stuck_behavior = Ground; return n; @@ -1205,9 +1072,8 @@ void Bot::GenerateAppearance() { iBeardColor = zone->random.Int(0, 3); } else if (GetGender()) { iHair = zone->random.Int(0, 2); - if (GetRace() == DWARF) { // Dwarven Females can have a beard - if(zone->random.Int(1, 100) < 50) - iFace += 10; + if (GetRace() == DWARF && zone->random.Int(1, 100) < 50) { + iFace += 10; } } else { iHair = zone->random.Int(0, 3); @@ -1222,11 +1088,11 @@ void Bot::GenerateAppearance() { iHairColor = zone->random.Int(0, 19); } - uint8 iEyeColor1 = (uint8)zone->random.Int(0, 9); + auto iEyeColor1 = (uint8)zone->random.Int(0, 9); uint8 iEyeColor2 = 0; if (GetRace() == DRAKKIN) { iEyeColor1 = iEyeColor2 = (uint8)zone->random.Int(0, 11); - } else if(zone->random.Int(1, 100) > 96) { + } else if (zone->random.Int(1, 100) > 96) { iEyeColor2 = zone->random.Int(0, 9); } else { iEyeColor2 = iEyeColor1; @@ -1252,393 +1118,37 @@ void Bot::GenerateAppearance() { drakkin_details = iDetails; } -int32 Bot::acmod() { - int agility = GetAGI(); - int level = GetLevel(); - if(agility < 1 || level < 1) - return 0; - - if(agility <= 74) { - if(agility == 1) - return -24; - else if(agility <= 3) - return -23; - else if(agility == 4) - return -22; - else if(agility <= 6) - return -21; - else if(agility <= 8) - return -20; - else if(agility == 9) - return -19; - else if(agility <= 11) - return -18; - else if(agility == 12) - return -17; - else if(agility <= 14) - return -16; - else if(agility <= 16) - return -15; - else if(agility == 17) - return -14; - else if(agility <= 19) - return -13; - else if(agility == 20) - return -12; - else if(agility <= 22) - return -11; - else if(agility <= 24) - return -10; - else if(agility == 25) - return -9; - else if(agility <= 27) - return -8; - else if(agility == 28) - return -7; - else if(agility <= 30) - return -6; - else if(agility <= 32) - return -5; - else if(agility == 33) - return -4; - else if(agility <= 35) - return -3; - else if(agility == 36) - return -2; - else if(agility <= 38) - return -1; - else if(agility <= 65) - return 0; - else if(agility <= 70) - return 1; - else if(agility <= 74) - return 5; - } else if(agility <= 137) { - if(agility == 75) { - if(level <= 6) - return 9; - else if(level <= 19) - return 23; - else if(level <= 39) - return 33; - else - return 39; - } else if(agility >= 76 && agility <= 79) { - if(level <= 6) - return 10; - else if(level <= 19) - return 23; - else if(level <= 39) - return 33; - else - return 40; - } else if(agility == 80) { - if(level <= 6) - return 11; - else if(level <= 19) - return 24; - else if(level <= 39) - return 34; - else - return 41; - } else if(agility >= 81 && agility <= 85) { - if(level <= 6) - return 12; - else if(level <= 19) - return 25; - else if(level <= 39) - return 35; - else - return 42; - } else if(agility >= 86 && agility <= 90) { - if(level <= 6) - return 12; - else if(level <= 19) - return 26; - else if(level <= 39) - return 36; - else - return 42; - } else if(agility >= 91 && agility <= 95) { - if(level <= 6) - return 13; - else if(level <= 19) - return 26; - else if(level <= 39) - return 36; - else - return 43; - } else if(agility >= 96 && agility <= 99) { - if(level <= 6) - return 14; - else if(level <= 19) - return 27; - else if(level <= 39) - return 37; - else - return 44; - } else if(agility == 100 && level >= 7) { - if(level <= 19) - return 28; - else if (level <= 39) - return 38; - else - return 45; - } - else if(level <= 6) - return 15; - //level is >6 - else if(agility >= 101 && agility <= 105) { - if(level <= 19) - return 29; - else if(level <= 39) - return 39;// not verified - else - return 45; - } else if(agility >= 106 && agility <= 110) { - if(level <= 19) - return 29; - else if(level <= 39) - return 39;// not verified - else - return 46; - } else if(agility >= 111 && agility <= 115) { - if(level <= 19) - return 30; - else if(level <= 39) - return 40;// not verified - else - return 47; - } else if(agility >= 116 && agility <= 119) { - if(level <= 19) - return 31; - else if(level <= 39) - return 41; - else - return 47; - } - else if(level <= 19) - return 32; - //level is > 19 - else if(agility == 120) { - if(level <= 39) - return 42; - else - return 48; - } else if(agility <= 125) { - if(level <= 39) - return 42; - else - return 49; - } else if(agility <= 135) { - if(level <= 39) - return 42; - else - return 50; - } else { - if(level <= 39) - return 42; - else - return 51; - } - } else if(agility <= 300) { - if(level <= 6) { - if(agility <= 139) - return 21; - else if(agility == 140) - return 22; - else if(agility <= 145) - return 23; - else if(agility <= 150) - return 23; - else if(agility <= 155) - return 24; - else if(agility <= 159) - return 25; - else if(agility == 160) - return 26; - else if(agility <= 165) - return 26; - else if(agility <= 170) - return 27; - else if(agility <= 175) - return 28; - else if(agility <= 179) - return 28; - else if(agility == 180) - return 29; - else if(agility <= 185) - return 30; - else if(agility <= 190) - return 31; - else if(agility <= 195) - return 31; - else if(agility <= 199) - return 32; - else if(agility <= 219) - return 33; - else if(agility <= 239) - return 34; - else - return 35; - } else if(level <= 19) { - if(agility <= 139) - return 34; - else if(agility == 140) - return 35; - else if(agility <= 145) - return 36; - else if(agility <= 150) - return 37; - else if(agility <= 155) - return 37; - else if(agility <= 159) - return 38; - else if(agility == 160) - return 39; - else if(agility <= 165) - return 40; - else if(agility <= 170) - return 40; - else if(agility <= 175) - return 41; - else if(agility <= 179) - return 42; - else if(agility == 180) - return 43; - else if(agility <= 185) - return 43; - else if(agility <= 190) - return 44; - else if(agility <= 195) - return 45; - else if(agility <= 199) - return 45; - else if(agility <= 219) - return 46; - else if(agility <= 239) - return 47; - else - return 48; - } else if(level <= 39) { - if(agility <= 139) - return 44; - else if(agility == 140) - return 45; - else if(agility <= 145) - return 46; - else if(agility <= 150) - return 47; - else if(agility <= 155) - return 47; - else if(agility <= 159) - return 48; - else if(agility == 160) - return 49; - else if(agility <= 165) - return 50; - else if(agility <= 170) - return 50; - else if(agility <= 175) - return 51; - else if(agility <= 179) - return 52; - else if(agility == 180) - return 53; - else if(agility <= 185) - return 53; - else if(agility <= 190) - return 54; - else if(agility <= 195) - return 55; - else if(agility <= 199) - return 55; - else if(agility <= 219) - return 56; - else if(agility <= 239) - return 57; - else - return 58; - } else { //lvl >= 40 - if(agility <= 139) - return 51; - else if(agility == 140) - return 52; - else if(agility <= 145) - return 53; - else if(agility <= 150) - return 53; - else if(agility <= 155) - return 54; - else if(agility <= 159) - return 55; - else if(agility == 160) - return 56; - else if(agility <= 165) - return 56; - else if(agility <= 170) - return 57; - else if(agility <= 175) - return 58; - else if(agility <= 179) - return 58; - else if(agility == 180) - return 59; - else if(agility <= 185) - return 60; - else if(agility <= 190) - return 61; - else if(agility <= 195) - return 61; - else if(agility <= 199) - return 62; - else if(agility <= 219) - return 63; - else if(agility <= 239) - return 64; - else - return 65; - } - } - else - return (65 + ((agility - 300) / 21)); - - LogError("Agility [{}] Level [{}]",agility,level); - return 0; -} - uint16 Bot::GetPrimarySkillValue() { EQ::skills::SkillType skill = EQ::skills::HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill - bool equiped = m_inv.GetItem(EQ::invslot::slotPrimary); - if(!equiped) + if (bool equiped = m_inv.GetItem(EQ::invslot::slotPrimary); !equiped) { skill = EQ::skills::SkillHandtoHand; - else { + } else { uint8 type = m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->ItemType; //is this the best way to do this? - switch(type) { - case EQ::item::ItemType1HSlash: - skill = EQ::skills::Skill1HSlashing; - break; - case EQ::item::ItemType2HSlash: - skill = EQ::skills::Skill2HSlashing; - break; - case EQ::item::ItemType1HPiercing: - skill = EQ::skills::Skill1HPiercing; - break; - case EQ::item::ItemType1HBlunt: - skill = EQ::skills::Skill1HBlunt; - break; - case EQ::item::ItemType2HBlunt: - skill = EQ::skills::Skill2HBlunt; - break; - case EQ::item::ItemType2HPiercing: - skill = EQ::skills::Skill2HPiercing; - break; - case EQ::item::ItemTypeMartial: - skill = EQ::skills::SkillHandtoHand; - break; - default: - skill = EQ::skills::SkillHandtoHand; - break; + switch (type) { + case EQ::item::ItemType1HSlash: + skill = EQ::skills::Skill1HSlashing; + break; + case EQ::item::ItemType2HSlash: + skill = EQ::skills::Skill2HSlashing; + break; + case EQ::item::ItemType1HPiercing: + skill = EQ::skills::Skill1HPiercing; + break; + case EQ::item::ItemType1HBlunt: + skill = EQ::skills::Skill1HBlunt; + break; + case EQ::item::ItemType2HBlunt: + skill = EQ::skills::Skill2HBlunt; + break; + case EQ::item::ItemType2HPiercing: + skill = EQ::skills::Skill2HPiercing; + break; + case EQ::item::ItemTypeMartial: + skill = EQ::skills::SkillHandtoHand; + break; + default: + skill = EQ::skills::SkillHandtoHand; + break; } } @@ -1652,7 +1162,7 @@ uint16 Bot::MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) uint32 Bot::GetTotalATK() { uint32 AttackRating = 0; uint32 WornCap = itembonuses.ATK; - if(IsBot()) { + if (IsBot()) { AttackRating = ((WornCap * 1.342) + (GetSkill(EQ::skills::SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69)); AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); if (AttackRating < 10) @@ -1667,7 +1177,7 @@ uint32 Bot::GetTotalATK() { uint32 Bot::GetATKRating() { uint32 AttackRating = 0; - if(IsBot()) { + if (IsBot()) { AttackRating = (GetSkill(EQ::skills::SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69); if (AttackRating < 10) AttackRating = 10; @@ -1683,26 +1193,26 @@ int32 Bot::GenerateBaseHitPoints() { int32 NormalSTA = GetSTA(); if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { float SoDPost255; - if(((NormalSTA - 255) / 2) > 0) + if (((NormalSTA - 255) / 2) > 0) SoDPost255 = ((NormalSTA - 255) / 2); else SoDPost255 = 0; int hp_factor = GetClassHPFactor(); - if(level < 41) + if (level < 41) new_base_hp = (5 + (GetLevel() * hp_factor / 12) + ((NormalSTA - SoDPost255) * GetLevel() * hp_factor / 3600)); - else if(level < 81) + else if (level < 81) new_base_hp = (5 + (40 * hp_factor / 12) + ((GetLevel() - 40) * hp_factor / 6) + ((NormalSTA - SoDPost255) * hp_factor / 90) + ((NormalSTA - SoDPost255) * (GetLevel() - 40) * hp_factor / 1800)); else new_base_hp = (5 + (80 * hp_factor / 8) + ((GetLevel() - 80) * hp_factor / 10) + ((NormalSTA - SoDPost255) * hp_factor / 90) + ((NormalSTA - SoDPost255) * hp_factor / 45)); } else { - if(((NormalSTA - 255) / 2) > 0) + if (((NormalSTA - 255) / 2) > 0) Post255 = ((NormalSTA - 255) / 2); else Post255 = 0; - new_base_hp = (5) + (GetLevel() * lm / 10) + (((NormalSTA - Post255) * GetLevel() * lm / 3000)) + ((Post255 * 1) * lm / 6000); + new_base_hp = 5 + (GetLevel() * lm / 10) + ((NormalSTA - Post255) * GetLevel() * lm / 3000) + ((Post255 * 1) * lm / 6000); } base_hp = new_base_hp; return new_base_hp; @@ -1719,7 +1229,7 @@ void Bot::LoadAAs() { AA::Ability *ability = (*iter).second.get(); //skip expendables - if(!ability->first || ability->charges > 0) { + if (!ability->first || ability->charges > 0) { ++iter; continue; } @@ -1735,7 +1245,7 @@ void Bot::LoadAAs() { } while(current) { - if(!CanUseAlternateAdvancementRank(current)) { + if (!CanUseAlternateAdvancementRank(current)) { current = nullptr; } else { current = current->next; @@ -1743,7 +1253,7 @@ void Bot::LoadAAs() { } } - if(points > 0) { + if (points > 0) { SetAA(id, points); } @@ -1760,8 +1270,7 @@ bool Bot::IsValidRaceClassCombo(uint16 bot_race, uint8 bot_class) { bool is_valid = false; auto classes = database.botdb.GetRaceClassBitmask(bot_race); - auto bot_class_bitmask = GetPlayerClassBit(bot_class); - if (classes & bot_class_bitmask) { + if (auto bot_class_bitmask = GetPlayerClassBit(bot_class); classes & bot_class_bitmask) { is_valid = true; } return is_valid; @@ -1797,7 +1306,7 @@ bool Bot::Save() std::string error_message; - if(!GetBotID()) { // New bot record + if (!GetBotID()) { // New bot record uint32 bot_id = 0; if (!database.botdb.SaveNewBot(this, bot_id) || !bot_id) { bot_owner->Message(Chat::White, "%s '%s'", BotDatabase::fail::SaveNewBot(), GetCleanName()); @@ -1865,8 +1374,13 @@ bool Bot::DeleteBot() return false; } - if (GetGroup()) + if (GetGroup()) { RemoveBotFromGroup(this, GetGroup()); + } + + if (GetRaid()) { + RemoveBotFromRaid(this); + } std::string error_message; @@ -2035,18 +1549,21 @@ bool Bot::LoadPet() bool Bot::SavePet() { - if (!GetPet() || GetPet()->IsFamiliar()) // dead? + if (!GetPet() || GetPet()->IsFamiliar()) { // dead? return true; + } NPC *pet_inst = GetPet()->CastToNPC(); - if (!pet_inst->GetPetSpellID() || !IsValidSpell(pet_inst->GetPetSpellID())) + if (!pet_inst->GetPetSpellID() || !IsValidSpell(pet_inst->GetPetSpellID())) { return false; + } auto bot_owner = GetBotOwner(); - if (!bot_owner) + if (!bot_owner) { return false; + } - char* pet_name = new char[64]; + auto pet_name = new char[64]; SpellBuff_Struct pet_buffs[PET_BUFF_COUNT]; uint32 pet_items[EQ::invslot::EQUIPMENT_COUNT]; @@ -2057,7 +1574,7 @@ bool Bot::SavePet() pet_inst->GetPetState(pet_buffs, pet_items, pet_name); std::string pet_name_str = pet_name; - safe_delete_array(pet_name); + safe_delete_array(pet_name) std::string error_message; @@ -2118,7 +1635,7 @@ bool Bot::Process() if (GetDepop()) { - _botOwner = 0; + _botOwner = nullptr; _botOwnerCharacterID = 0; return false; @@ -2140,10 +1657,8 @@ bool Bot::Process() if (tic_timer.Check()) { // 6 seconds, or whatever the rule is set to has passed, send this position to everyone to avoid ghosting - if (!IsEngaged()) { - if (!rest_timer.Enabled()) { - rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); - } + if (!IsEngaged() && !rest_timer.Enabled()) { + rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); } BuffProcess(); @@ -2206,7 +1721,6 @@ bool Bot::Process() return true; } - // Bot AI AI_Process(); return true; @@ -2240,25 +1754,22 @@ void Bot::AI_Bot_Init() AIautocastspell_timer.reset(nullptr); casting_spell_AIindex = static_cast(AIBot_spells.size()); - m_roambox.max_x = 0; - m_roambox.max_y = 0; - m_roambox.min_x = 0; - m_roambox.min_y = 0; + m_roambox.max_x = 0; + m_roambox.max_y = 0; + m_roambox.min_x = 0; + m_roambox.min_y = 0; m_roambox.distance = 0; - m_roambox.dest_x = 0; - m_roambox.dest_y = 0; - m_roambox.dest_z = 0; - m_roambox.delay = 2500; + m_roambox.dest_x = 0; + m_roambox.dest_y = 0; + m_roambox.dest_z = 0; + m_roambox.delay = 2500; m_roambox.min_delay = 2500; } void Bot::SpellProcess() { - if(spellend_timer.Check(false)) { + if (spellend_timer.Check(false)) { NPC::SpellProcess(); - if(GetClass() == BARD) { - if (casting_spell_id != 0) - casting_spell_id = 0; - } + if (GetClass() == BARD && casting_spell_id != 0) casting_spell_id = 0; } } @@ -2283,49 +1794,48 @@ void Bot::BotMeditate(bool isSitting) { void Bot::BotRangedAttack(Mob* other) { //make sure the attack and ranged timers are up //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow - if((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) { + if ((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) { LogCombatDetail("Bot Archery attack canceled. Timer not up. Attack [{}] ranged [{}]", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); Message(0, "Error: Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); return; } - EQ::ItemInstance* rangedItem = GetBotItem(EQ::invslot::slotRange); + const auto rangedItem = GetBotItem(EQ::invslot::slotRange); const EQ::ItemData* RangeWeapon = nullptr; - if(rangedItem) + if (rangedItem) RangeWeapon = rangedItem->GetItem(); - EQ::ItemInstance* ammoItem = GetBotItem(EQ::invslot::slotAmmo); + const auto ammoItem = GetBotItem(EQ::invslot::slotAmmo); const EQ::ItemData* Ammo = nullptr; - if(ammoItem) + if (ammoItem) Ammo = ammoItem->GetItem(); - if(!RangeWeapon || !Ammo) + if (!RangeWeapon || !Ammo) return; LogCombatDetail("Shooting [{}] with bow [{}] ([{}]) and arrow [{}] ([{}])", other->GetCleanName(), RangeWeapon->Name, RangeWeapon->ID, Ammo->Name, Ammo->ID); - if(!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) + if (!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) return; SendItemAnimation(other, Ammo, EQ::skills::SkillArchery); - //DoArcheryAttackDmg(GetTarget(), rangedItem, ammoItem); DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch //break invis when you attack - if(invisible) { + if (invisible) { LogCombatDetail("Removing invisibility due to melee attack"); BuffFadeByEffect(SE_Invisibility); BuffFadeByEffect(SE_Invisibility2); invisible = false; } - if(invisible_undead) { + if (invisible_undead) { LogCombatDetail("Removing invisibility vs. undead due to melee attack"); BuffFadeByEffect(SE_InvisVsUndead); BuffFadeByEffect(SE_InvisVsUndead2); invisible_undead = false; } - if(invisible_animals) { + if (invisible_animals) { LogCombatDetail("Removing invisibility vs. animals due to melee attack"); BuffFadeByEffect(SE_InvisVsAnimals); invisible_animals = false; @@ -2337,8 +1847,8 @@ void Bot::BotRangedAttack(Mob* other) { if (hidden || improved_hidden) { hidden = false; improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + auto sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; sa_out->spawn_id = GetID(); sa_out->type = 0x03; sa_out->parameter = 0; @@ -2368,35 +1878,19 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { //A reasonable forumla would then be TA = 20% * chance //AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack) //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. - if(tripleAttack) { + if (tripleAttack) { // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] int32 triple_bonus = (spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance); chance *= 0.2f; //Baseline chance is 20% of your double attack chance. chance *= (float(100.0f + triple_bonus) / 100.0f); //Apply modifiers. } - if((zone->random.Real(0, 1) < chance)) + if (zone->random.Real(0, 1) < chance) return true; return false; } -bool Bot::CanDoSpecialAttack(Mob *other) { - //Make sure everything is valid before doing any attacks. - if (!other) { - SetTarget(nullptr); - return false; - } - - if(!GetTarget()) - SetTarget(other); - - if ((other == nullptr || ((GetAppearance() == eaDead) || (other->IsClient() && other->CastToClient()->IsDead())) || HasDied() || (!IsAttackAllowed(other))) || other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)) - return false; - - return true; -} - void Bot::SetTarget(Mob *mob) { if (mob != this) { @@ -2430,15 +1924,27 @@ void Bot::SetHoldMode() { // AI Processing for the Bot object constexpr float MAX_CASTER_DISTANCE[PLAYER_CLASS_COUNT] = { - 0, (34 * 34), (24 * 24), (28 * 28), (26 * 26), (42 * 42), 0, (30 * 30), 0, (38 * 38), (54 * 54), (48 * 48), (52 * 52), (50 * 50), (32 * 32), 0 -// W C P R S D M B R S N W M E B B -// A L A N H R N R O H E I A N S E -// R R L G D U K D G M C Z G C T R + 0, + (34 * 34), + (24 * 24), + (28 * 28), + (26 * 26), + (42 * 42), + 0, + (30 * 30), + 0, + (38 * 38), + (54 * 54), + (48 * 48), + (52 * 52), + (50 * 50), + (32 * 32), + 0 }; void Bot::AI_Process() { -#define TEST_COMBATANTS() if (!GetTarget() || GetAppearance() == eaDead) { return; } + #define PULLING_BOT (GetPullingFlag() || GetReturningFlag()) #define NOT_PULLING_BOT (!GetPullingFlag() && !GetReturningFlag()) #define GUARDING (GetGuardFlag()) @@ -2449,286 +1955,71 @@ void Bot::AI_Process() #define NOT_PASSIVE (GetBotStance() != EQ::constants::stancePassive) Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); - Group* bot_group = GetGroup(); + auto raid = entity_list.GetRaidByBotName(GetName()); + uint32 r_group = RAID_GROUPLESS; + if (raid) { + raid->VerifyRaid(); + r_group = raid->GetGroup(GetName()); -//#pragma region PRIMARY AI SKIP CHECKS + if (mana_timer.Check(false)) { + raid->SendHPManaEndPacketsFrom(this); + } + + if (send_hp_update_timer.Check(false)) { + raid->SendHPManaEndPacketsFrom(this); + } + } + + auto bot_group = GetGroup(); // Primary reasons for not processing AI - if (!bot_owner || !bot_group || !IsAIControlled()) { + if (!IsAIProcessValid(bot_owner, bot_group, raid)) { return; } - if (bot_owner->IsDead()) { + auto leash_owner = SetLeashOwner(bot_owner, bot_group, raid, r_group); - SetTarget(nullptr); - SetBotOwner(nullptr); - - return; - } - - // We also need a leash owner and follow mob (subset of primary AI criteria) - bot_group->VerifyGroup(); - Client* leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); if (!leash_owner) { return; } -//#pragma endregion + SetFollowID(leash_owner->GetID()); - Mob* follow_mob = entity_list.GetMob(GetFollowID()); - if (!follow_mob) { + auto follow_mob = SetFollowMob(leash_owner); - follow_mob = leash_owner; - SetFollowID(leash_owner->GetID()); - } - - // Berserk updates should occur if primary AI criteria are met - if (GetClass() == WARRIOR || GetClass() == BERSERKER) { - - if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { - - entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); - berserk = true; - } - - if (berserk && GetHPRatio() >= 30.0f) { - - entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); - berserk = false; - } - } - -//#pragma region SECONDARY AI SKIP CHECKS + SetBerserkState(); // Secondary reasons for not processing AI - if (GetPauseAI() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { - - if (IsCasting()) { - InterruptSpell(); - } - - if (IsMyHealRotationSet() || (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this)) { - - AdvanceHealRotation(false); - m_member_of_heal_rotation->SetMemberIsCasting(this, false); - } - + if (CheckIfIncapacitated()) { return; } -//#pragma endregion - float fm_distance = DistanceSquared(m_Position, follow_mob->GetPosition()); float lo_distance = DistanceSquared(m_Position, leash_owner->GetPosition()); float leash_distance = RuleR(Bots, LeashDistance); -//#pragma region CURRENTLY CASTING CHECKS +// CURRENTLY CASTING CHECKS - if (IsCasting()) { - - if (IsHealRotationMember() && - m_member_of_heal_rotation->CastingOverride() && - m_member_of_heal_rotation->CastingTarget() != nullptr && - m_member_of_heal_rotation->CastingReady() && - m_member_of_heal_rotation->CastingMember() == this && - !m_member_of_heal_rotation->MemberIsCasting(this)) - { - InterruptSpell(); - } - else if (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this) { - - AdvanceHealRotation(false); - return; - } - else if (GetClass() != BARD) { - - if (IsEngaged()) { - return; - } - - if ( - (NOT_GUARDING && fm_distance > GetFollowDistance()) || // Cancel out-of-combat casting if movement to follow mob is required - (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) > GetFollowDistance()) // Cancel out-of-combat casting if movement to guard point is required - ) { - InterruptSpell(); - } - - return; - } - } - else if (IsHealRotationMember()) { - m_member_of_heal_rotation->SetMemberIsCasting(this, false); - } - -//#pragma endregion - - // Can't move if rooted... - if (IsRooted() && IsMoving()) { - - StopMoving(); + if (CheckIfCasting(fm_distance)) { return; } -//#pragma region HEAL ROTATION CASTING CHECKS +// HEAL ROTATION CASTING CHECKS - if (IsMyHealRotationSet()) { - - if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { - - m_member_of_heal_rotation->SetMemberIsCasting(this); - m_member_of_heal_rotation->UpdateTargetHealingStats(HealRotationTarget()); - AdvanceHealRotation(); - } - else { - - m_member_of_heal_rotation->SetMemberIsCasting(this, false); - AdvanceHealRotation(false); - } - } - -//#pragma endregion + HealRotationChecks(); bool bo_alt_combat = (RuleB(Bots, AllowOwnerOptionAltCombat) && bot_owner->GetBotOption(Client::booAltCombat)); -//#pragma region ATTACK FLAG - if (GetAttackFlag()) { // Push owner's target onto our hate list - - if (GetPet() && PULLING_BOT) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - - SetAttackFlag(false); - SetAttackingFlag(false); - SetPullFlag(false); - SetPullingFlag(false); - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - - if (NOT_HOLDING && NOT_PASSIVE) { - - auto attack_target = bot_owner->GetTarget(); - if (attack_target) { - - InterruptSpell(); - WipeHateList(); - AddToHateList(attack_target, 1); - SetTarget(attack_target); - SetAttackingFlag(); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - - GetPet()->WipeHateList(); - GetPet()->AddToHateList(attack_target, 1); - GetPet()->SetTarget(attack_target); - } - } - } + SetOwnerTarget(bot_owner); } - -//#pragma endregion - -//#pragma region PULL FLAG - else if (GetPullFlag()) { // Push owner's target onto our hate list and set flags so other bots do not aggro - - SetAttackFlag(false); - SetAttackingFlag(false); - SetPullFlag(false); - SetPullingFlag(false); - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - - if (NOT_HOLDING && NOT_PASSIVE) { - - auto pull_target = bot_owner->GetTarget(); - if (pull_target) { - - BotGroupSay( - this, - fmt::format( - "Pulling {}.", - pull_target->GetCleanName() - ).c_str() - ); - InterruptSpell(); - WipeHateList(); - AddToHateList(pull_target, 1); - SetTarget(pull_target); - SetPullingFlag(); - bot_owner->SetBotPulling(); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { - - GetPet()->WipeHateList(); - GetPet()->SetTarget(nullptr); - m_previous_pet_order = GetPet()->GetPetOrder(); - GetPet()->SetPetOrder(SPO_Guard); - } - } - } + BotPullerProcess(bot_owner, raid); } -//#pragma endregion +//ALT COMBAT (ACQUIRE HATE) -//#pragma region ALT COMBAT (ACQUIRE HATE) - - else if (bo_alt_combat && m_alt_combat_hate_timer.Check(false)) { // 'Alt Combat' gives some more 'control' options on how bots process aggro - - // Empty hate list - let's find some aggro - if (!IsEngaged() && NOT_HOLDING && NOT_PASSIVE && (!bot_owner->GetBotPulling() || NOT_PULLING_BOT)) { - - Mob* lo_target = leash_owner->GetTarget(); - if (lo_target && - lo_target->IsNPC() && - !lo_target->IsMezzed() && - ((bot_owner->GetBotOption(Client::booAutoDefend) && lo_target->GetHateAmount(leash_owner)) || leash_owner->AutoAttackEnabled()) && - lo_distance <= leash_distance && - DistanceSquared(m_Position, lo_target->GetPosition()) <= leash_distance && - (CheckLosFN(lo_target) || leash_owner->CheckLosFN(lo_target)) && - IsAttackAllowed(lo_target)) - { - AddToHateList(lo_target, 1); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - - GetPet()->AddToHateList(lo_target, 1); - GetPet()->SetTarget(lo_target); - } - } - else { - - for (int counter = 0; counter < bot_group->GroupCount(); counter++) { - - Mob* bg_member = bot_group->members[counter]; - if (!bg_member) { - continue; - } - - Mob* bgm_target = bg_member->GetTarget(); - if (!bgm_target || !bgm_target->IsNPC()) { - continue; - } - - if (!bgm_target->IsMezzed() && - ((bot_owner->GetBotOption(Client::booAutoDefend) && bgm_target->GetHateAmount(bg_member)) || leash_owner->AutoAttackEnabled()) && - lo_distance <= leash_distance && - DistanceSquared(m_Position, bgm_target->GetPosition()) <= leash_distance && - (CheckLosFN(bgm_target) || leash_owner->CheckLosFN(bgm_target)) && - IsAttackAllowed(bgm_target)) - { - AddToHateList(bgm_target, 1); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - - GetPet()->AddToHateList(bgm_target, 1); - GetPet()->SetTarget(bgm_target); - } - - break; - } - } - } - } - } - -//#pragma endregion + SetBotTarget(bot_owner, raid, bot_group, leash_owner, lo_distance, leash_distance, bo_alt_combat); glm::vec3 Goal(0, 0, 0); @@ -2739,242 +2030,51 @@ void Bot::AI_Process() rest_timer.Disable(); } -//#pragma region PULLING FLAG (TARGET VALIDATION) +// PULLING FLAG (TARGET VALIDATION) if (GetPullingFlag()) { - - if (!GetTarget()) { - - WipeHateList(); - SetTarget(nullptr); - SetPullingFlag(false); - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - + if (!PullingFlagChecks(bot_owner)) { return; } - else if (GetTarget()->GetHateList().size()) { - - WipeHateList(); - SetTarget(nullptr); - SetPullingFlag(false); - SetReturningFlag(); - - return; - } - else { - // Default action is to aggress towards enemy - } } -//#pragma endregion - -//#pragma region RETURNING FLAG +// RETURNING FLAG else if (GetReturningFlag()) { - - // Need to make it back to group before clearing return flag - if (fm_distance <= GetFollowDistance()) { - - // Once we're back, clear blocking flags so everyone else can join in - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - } - - // Need to keep puller out of combat until they reach their 'return to' destination - if (HasTargetReflection()) { - - SetTarget(nullptr); - WipeHateList(); - + if (!ReturningFlagChecks(bot_owner, fm_distance)) { return; } } -//#pragma endregion - -//#pragma region ALT COMBAT (ACQUIRE TARGET) +// ALT COMBAT (ACQUIRE TARGET) else if (bo_alt_combat && m_alt_combat_hate_timer.Check()) { // Find a mob from hate list to target - - // Group roles can be expounded upon in the future - auto assist_mob = entity_list.GetMob(bot_group->GetMainAssistName()); - bool find_target = true; - - if (assist_mob) { - if (assist_mob->GetTarget()) { - if (assist_mob != this) { - if (GetTarget() != assist_mob->GetTarget()) { - SetTarget(assist_mob->GetTarget()); - } - - if ( - HasPet() && - ( - GetClass() != ENCHANTER || - GetPet()->GetPetType() != petAnimation || - GetAA(aaAnimationEmpathy) >= 2 - ) - ) { - // This artificially inflates pet's target aggro..but, less expensive than checking hate each AI process - GetPet()->AddToHateList(assist_mob->GetTarget(), 1); - GetPet()->SetTarget(assist_mob->GetTarget()); - } - } - - find_target = false; - } else if (assist_mob != this) { - if (GetTarget()) { - SetTarget(nullptr); - } - - if ( - HasPet() && - ( - GetClass() != ENCHANTER || - GetPet()->GetPetType() != petAnimation || - GetAA(aaAnimationEmpathy) >= 1 - ) - ) { - GetPet()->WipeHateList(); - GetPet()->SetTarget(nullptr); - } - - find_target = false; - } - } - - if (find_target) { - if (IsRooted()) { - auto closest = hate_list.GetClosestEntOnHateList(this, true); - if (closest) { - SetTarget(closest); - } - } else { - // This will keep bots on target for now..but, future updates will allow for rooting/stunning - auto escaping = hate_list.GetEscapingEntOnHateList(leash_owner, leash_distance); - if (escaping) { - SetTarget(escaping); - } - - if (!GetTarget()) { - auto most_hate = hate_list.GetEntWithMostHateOnList(this, nullptr, true); - if (most_hate) { - SetTarget(most_hate); - } - } - } - } + AcquireBotTarget(bot_group, nullptr, leash_owner, leash_distance); } -//#pragma endregion +// DEFAULT (ACQUIRE TARGET) -//#pragma region DEFAULT (ACQUIRE TARGET) - - else { - - // Default behavior doesn't have a means of acquiring a target from the bot's hate list.. - // ..that action occurs through commands or out-of-combat checks - // (Use current target, if already in combat) - } - -//#pragma endregion - -//#pragma region VERIFY TARGET AND STANCE - - Mob* tar = GetTarget(); // We should have a target..if not, we're awaiting new orders - if (!tar || PASSIVE) { - if (GetTarget()) { - SetTarget(nullptr); - } - - WipeHateList(); - SetAttackFlag(false); - SetAttackingFlag(false); - if (PULLING_BOT) { - - // 'Flags' should only be set on the bot that is pulling - SetPullingFlag(false); - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - } - - if (GetArchetype() == ARCHETYPE_CASTER) { - BotMeditate(true); - } +// VERIFY TARGET AND STANCE + auto tar = GetBotTarget(bot_owner); + if (!tar) { return; } -//#pragma endregion - -//#pragma region ATTACKING FLAG (HATE VALIDATION) +// ATTACKING FLAG (HATE VALIDATION) if (GetAttackingFlag() && tar->CheckAggro(this)) { SetAttackingFlag(false); } -//#pragma endregion - float tar_distance = DistanceSquared(m_Position, tar->GetPosition()); -//#pragma region TARGET VALIDATION - - // DOUBLE-CHECK THIS CRITERIA - - // Verify that our target has attackable criteria - if (HOLDING || - !tar->IsNPC() || - tar->IsMezzed() || - lo_distance > leash_distance || - tar_distance > leash_distance || - (!GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) || // This is suppose to keep bots from attacking things behind walls - !IsAttackAllowed(tar) || - (bo_alt_combat && - (!GetAttackingFlag() && NOT_PULLING_BOT && !leash_owner->AutoAttackEnabled() && !tar->GetHateAmount(this) && !tar->GetHateAmount(leash_owner)) - ) - ) - { - // Normally, we wouldn't want to do this without class checks..but, too many issues can arise if we let enchanter animation pets run rampant - if (HasPet()) { - - GetPet()->RemoveFromHateList(tar); - GetPet()->SetTarget(nullptr); - } - - RemoveFromHateList(tar); - SetTarget(nullptr); - - SetAttackFlag(false); - SetAttackingFlag(false); - if (PULLING_BOT) { - - SetPullingFlag(false); - SetReturningFlag(false); - bot_owner->SetBotPulling(false); - if (GetPet()) { - GetPet()->SetPetOrder(m_previous_pet_order); - } - } - - if (IsMoving()) { - StopMoving(); - } +// TARGET VALIDATION + if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, bo_alt_combat, tar, tar_distance)) { return; } -//#pragma endregion - // This causes conflicts with default pet handler (bounces between targets) if (NOT_PULLING_BOT && HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { @@ -2986,11 +2086,11 @@ void Bot::AI_Process() return; } - if (!(GetPlayerState() & static_cast(PlayerState::Aggressive))) { + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) { SendAddPlayerState(PlayerState::Aggressive); } -//#pragma region PULLING FLAG (ACTIONABLE RANGE) +// PULLING FLAG (ACTIONABLE RANGE) if (GetPullingFlag()) { @@ -3004,308 +2104,31 @@ void Bot::AI_Process() } } -//#pragma endregion +// COMBAT RANGE CALCS -//#pragma region COMBAT RANGE CALCS + bool atCombatRange; + const EQ::ItemInstance* p_item; + const EQ::ItemInstance* s_item; + CheckCombatRange(tar, tar_distance, atCombatRange, p_item, s_item); - bool atCombatRange = false; - - const auto* p_item = GetBotItem(EQ::invslot::slotPrimary); - const auto* s_item = GetBotItem(EQ::invslot::slotSecondary); - - bool behind_mob = false; - bool backstab_weapon = false; - if (GetClass() == ROGUE) { - - behind_mob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use - backstab_weapon = p_item && p_item->GetItemBackstabDamage(); - } - - // Calculate melee distances - float melee_distance_max = 0.0f; - float melee_distance = 0.0f; - { - float size_mod = GetSize(); - float other_size_mod = tar->GetSize(); - - if (GetRace() == RT_DRAGON || GetRace() == RT_WURM || GetRace() == RT_DRAGON_7) { // For races with a fixed size - size_mod = 60.0f; - } - else if (size_mod < 6.0f) { - size_mod = 8.0f; - } - - if (tar->GetRace() == RT_DRAGON || tar->GetRace() == RT_WURM || tar->GetRace() == RT_DRAGON_7) { // For races with a fixed size - other_size_mod = 60.0f; - } - else if (other_size_mod < 6.0f) { - other_size_mod = 8.0f; - } - - if (other_size_mod > size_mod) { - size_mod = other_size_mod; - } - - if (size_mod > 29.0f) { - size_mod *= size_mod; - } - else if (size_mod > 19.0f) { - size_mod *= (size_mod * 2.0f); - } - else { - size_mod *= (size_mod * 4.0f); - } - - // Prevention of ridiculously sized hit boxes - if (size_mod > 10000.0f) { - size_mod = (size_mod / 7.0f); - } - - melee_distance_max = size_mod; - - switch (GetClass()) { - case WARRIOR: - case PALADIN: - case SHADOWKNIGHT: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.45f; - } - else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) { - melee_distance = melee_distance_max * 0.35f; - } - else { - melee_distance = melee_distance_max * 0.40f; - } - - break; - case NECROMANCER: - case WIZARD: - case MAGICIAN: - case ENCHANTER: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.95f; - } - else { - melee_distance = melee_distance_max * 0.75f; - } - - break; - case ROGUE: - if (behind_mob && backstab_weapon) { - if (p_item->GetItem()->IsType2HWeapon()) { // 'p_item' tested in 'backstab_weapon' check above - melee_distance = melee_distance_max * 0.30f; - } - else { - melee_distance = melee_distance_max * 0.25f; - } - - break; - } - // Fall-through - default: - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - melee_distance = melee_distance_max * 0.70f; - } - else { - melee_distance = melee_distance_max * 0.50f; - } - - break; - } - } - float melee_distance_min = melee_distance / 2.0f; - float caster_distance_max = GetBotCasterMaxRange(melee_distance_max); - - bool atArcheryRange = IsArcheryRange(tar); - - if (GetRangerAutoWeaponSelect()) { - - bool changeWeapons = false; - - if (atArcheryRange && !IsBotArcher()) { - - SetBotArcherySetting(true); - changeWeapons = true; - } - else if (!atArcheryRange && IsBotArcher()) { - - SetBotArcherySetting(false); - changeWeapons = true; - } - - if (changeWeapons) { - ChangeBotArcherWeapons(IsBotArcher()); - } - } - bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); - if (IsBotArcher() && atArcheryRange) { - atCombatRange = true; - } - else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) { - atCombatRange = true; - } - else if (tar_distance <= melee_distance) { - atCombatRange = true; - } - -//#pragma endregion - -//#pragma region ENGAGED AT COMBAT RANGE +// ENGAGED AT COMBAT RANGE // We can fight if (atCombatRange) { - //if (IsMoving() || GetCombatJitterFlag()) { // StopMoving() needs to be called so that the jitter timer can be reset if (IsMoving()) { - - // Since we're using a pseudo-shadowstep for jitter, disregard the combat jitter flag - //if (!GetCombatJitterFlag()) { - StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); - //} - + StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); return; } - // Combat 'jitter' code - // Note: Combat Jitter is disabled until a working movement solution can be found if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == BARD)) { - if (!IsRooted()) { - - if (HasTargetReflection()) { - - if (!tar->IsFeared() && !tar->IsStunned()) { - - if (GetClass() == ROGUE) { - - if (m_evade_timer.Check(false)) { // Attempt to evade - - int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; - if (timer_duration < 0) { - timer_duration = 0; - } - - m_evade_timer.Start(timer_duration); - if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { - RogueEvade(tar); - } - - return; - } - } - - //if (tar->IsRooted()) { // Move caster/rogue back from rooted mob - out of combat range, if necessary - - // if (GetArchetype() == ARCHETYPE_CASTER || GetClass() == ROGUE) { - - // if (tar_distance <= melee_distance_max) { - - // if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { - // //if (PlotPositionBehindMeFacingTarget(tar, Goal.x, Goal.y, Goal.z)) { - - // Teleport(Goal); - // //WalkTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // } - //} - } - } - //else { - - // if (caster_distance_min && tar_distance < caster_distance_min && !tar->IsFeared()) { // Caster back-off adjustment - - // if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { - // //if (PlotPositionBehindMeFacingTarget(tar, Goal.x, Goal.y, Goal.z)) { - - // if (DistanceSquared(Goal, tar->GetPosition()) <= caster_distance_max) { - - // Teleport(Goal); - // //WalkTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // } - // else if (tar_distance < melee_distance_min) { // Melee back-off adjustment - - // if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { - // //if (PlotPositionBehindMeFacingTarget(tar, Goal.x, Goal.y, Goal.z)) { - - // if (DistanceSquared(Goal, tar->GetPosition()) <= melee_distance_max) { - - // Teleport(Goal); - // //WalkTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // } - // else if (backstab_weapon && !behind_mob) { // Move the rogue to behind the mob - - // if (PlotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z)) { - // //if (PlotPositionOnArcBehindTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance)) { - - // float distance_squared = DistanceSquared(Goal, tar->GetPosition()); - // if (/*distance_squared >= melee_distance_min && */distance_squared <= melee_distance_max) { - - // Teleport(Goal); - // //RunTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // } - // else if (m_combat_jitter_timer.Check()) { - - // if (!caster_distance && PlotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z)) { - // //if (!caster_distance && PlotPositionOnArcInFrontOfTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance)) { - - // float distance_squared = DistanceSquared(Goal, tar->GetPosition()); - // if (/*distance_squared >= melee_distance_min && */distance_squared <= melee_distance_max) { - - // Teleport(Goal); - // //WalkTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // else if (caster_distance && PlotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z)) { - // //else if (caster_distance && PlotPositionOnArcInFrontOfTarget(tar, Goal.x, Goal.y, Goal.z, caster_distance)) { - - // float distance_squared = DistanceSquared(Goal, tar->GetPosition()); - // if (/*distance_squared >= caster_distance_min && */distance_squared <= caster_distance_max) { - - // Teleport(Goal); - // //WalkTo(Goal.x, Goal.y, Goal.z); - // SetCombatJitterFlag(); - - // return; - // } - // } - // } - - // if (!IsFacingMob(tar)) { - - // FaceTarget(tar); - // return; - // } - //} + if (TryEvade(tar)) { + return; } - else { - if (!IsSitting() && !IsFacingMob(tar)) { - - FaceTarget(tar); - return; - } + if (TryFacingTarget(tar)) { + return; } } @@ -3314,139 +2137,24 @@ void Bot::AI_Process() } // Up to this point, GetTarget() has been safe to dereference since the initial - // TEST_COMBATANTS() call. Due to the chance of the target dying and our pointer + // if (!GetTarget() || GetAppearance() == eaDead) { return false; } call. Due to the chance of the target dying and our pointer // being nullified, we need to test it before dereferencing to avoid crashes - if (IsBotArcher() && ranged_timer.Check(false)) { // Can shoot mezzed, stunned and dead!? - - TEST_COMBATANTS(); - if (GetTarget()->GetHPRatio() <= 99.0f) { - BotRangedAttack(tar); - } + if (IsBotArcher() && TryRangedAttack(tar)) { + return; } - else if (!IsBotArcher() && GetLevel() < GetStopMeleeLevel()) { - // We can't fight if we don't have a target, are stun/mezzed or dead.. - // Stop attacking if the target is enraged - TEST_COMBATANTS(); - if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { + if (!IsBotArcher() && GetLevel() < GetStopMeleeLevel()) { + if (!TryClassAttacks(tar)) { return; } - // First, special attack per class (kick, backstab etc..) - TEST_COMBATANTS(); - DoClassAttacks(tar); - - TEST_COMBATANTS(); - if (attack_timer.Check()) { // Process primary weapon attacks - - Attack(tar, EQ::invslot::slotPrimary); - - TEST_COMBATANTS(); - TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); - - TEST_COMBATANTS(); - TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); - - // bool tripleSuccess = false; - - TEST_COMBATANTS(); - if (CanThisClassDoubleAttack()) { - - if (CheckBotDoubleAttack()) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - - TEST_COMBATANTS(); - if (GetSpecialAbility(SPECATK_TRIPLE) && CheckBotDoubleAttack(true)) { - // tripleSuccess = true; - Attack(tar, EQ::invslot::slotPrimary, true); - } - - TEST_COMBATANTS(); - // quad attack, does this belong here?? - if (GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) { - Attack(tar, EQ::invslot::slotPrimary, true); - } - } - - TEST_COMBATANTS(); - // Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). - int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance); - if (flurrychance) { - - if (zone->random.Int(0, 100) < flurrychance) { - - MessageString(Chat::NPCFlurry, YOU_FLURRY); - Attack(tar, EQ::invslot::slotPrimary, false); - - TEST_COMBATANTS(); - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - - TEST_COMBATANTS(); - auto ExtraAttackChanceBonus = - (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + - aabonuses.ExtraAttackChance[0]); - if (ExtraAttackChanceBonus) { - - if (p_item && p_item->GetItem()->IsType2HWeapon()) { - - if (zone->random.Int(0, 100) < ExtraAttackChanceBonus) { - Attack(tar, EQ::invslot::slotPrimary, false); - } - } - } + if (!TryPrimaryWeaponAttacks(tar, p_item)) { + return; } - TEST_COMBATANTS(); - if (attack_dw_timer.Check() && CanThisClassDualWield()) { // Process secondary weapon attacks - - const EQ::ItemData* s_itemdata = nullptr; - // Can only dual wield without a weapon if you're a monk - if (s_item || (GetClass() == MONK)) { - - if (s_item) { - s_itemdata = s_item->GetItem(); - } - - int weapon_type = 0; // No weapon type. - bool use_fist = true; - if (s_itemdata) { - - weapon_type = s_itemdata->ItemType; - use_fist = false; - } - - if (use_fist || !s_itemdata->IsType2HWeapon()) { - - float DualWieldProbability = 0.0f; - - int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); - DualWieldProbability = ((GetSkill(EQ::skills::SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max - - int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); - DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); - - float random = zone->random.Real(0, 1); - if (random < DualWieldProbability) { // Max 78% of DW - - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand - - TEST_COMBATANTS(); - TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); - - TEST_COMBATANTS(); - if (CanThisClassDoubleAttack() && CheckBotDoubleAttack()) { - - if (tar->GetHP() > -10) { - Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand - } - } - } - } - } + if (!TrySecondaryWeaponAttacks(tar, s_item)) { + return; } } @@ -3455,68 +2163,15 @@ void Bot::AI_Process() } } -//#pragma endregion +// ENGAGED NOT AT COMBAT RANGE -//#pragma region ENGAGED NOT AT COMBAT RANGE + else if (!TryPursueTarget(leash_distance, Goal)) { + return; + } - else { // To far away to fight (GetTarget() validity can be iffy below this point - including outer scopes) - - // This code actually gets processed when we are too far away from target and have not engaged yet, too - if (/*!GetCombatJitterFlag() && */AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == BARD)) { // Pursue processing - - if (GetTarget() && !IsRooted()) { - - LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); - Goal = GetTarget()->GetPosition(); - if (DistanceSquared(m_Position, Goal) <= leash_distance) { - RunTo(Goal.x, Goal.y, Goal.z); - } - else { - - WipeHateList(); - SetTarget(nullptr); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - - GetPet()->WipeHateList(); - GetPet()->SetTarget(nullptr); - } - } - - return; - } - else { - - if (IsMoving()) { - StopMoving(); - } - return; - } - } - - if (GetTarget() && GetTarget()->IsFeared() && !spellend_timer.Enabled() && AI_think_timer->Check()) { - - if (!IsFacingMob(GetTarget())) { - FaceTarget(GetTarget()); - } - - // This is a mob that is fleeing either because it has been feared or is low on hitpoints - AI_PursueCastCheck(); // This appears to always return true..can't trust for success/fail - - return; - } - } // End not in combat range - -//#pragma endregion - - if (!IsMoving() && !spellend_timer.Enabled()) { // This may actually need work... - - if (GetTarget() && AI_EngagedCastCheck()) { - BotMeditate(false); - } - else if (GetArchetype() == ARCHETYPE_CASTER) { - BotMeditate(true); - } +// End not in combat range + if (TryMeditate()) { return; } } @@ -3530,60 +2185,12 @@ void Bot::AI_Process() SetReturningFlag(false); } -//#pragma region AUTO DEFEND +// AUTO DEFEND - // This is as close as I could get without modifying the aggro mechanics and making it an expensive process... - // 'class Client' doesn't make use of hate_list... - if (RuleB(Bots, AllowOwnerOptionAutoDefend) && bot_owner->GetBotOption(Client::booAutoDefend)) { - - if (!m_auto_defend_timer.Enabled()) { - - m_auto_defend_timer.Start(zone->random.Int(250, 1250)); // random timer to simulate 'awareness' (cuts down on scanning overhead) - return; - } - - if (m_auto_defend_timer.Check() && bot_owner->GetAggroCount()) { - - if (NOT_HOLDING && NOT_PASSIVE) { - - auto xhaters = bot_owner->GetXTargetAutoMgr(); - if (xhaters && !xhaters->empty()) { - - for (auto hater_iter : xhaters->get_list()) { - - if (!hater_iter.spawn_id) { - continue; - } - - if (bot_owner->GetBotPulling() && bot_owner->GetTarget() && hater_iter.spawn_id == bot_owner->GetTarget()->GetID()) { - continue; - } - - auto hater = entity_list.GetMob(hater_iter.spawn_id); - if (hater && !hater->IsMezzed() && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { - - // This is roughly equivilent to npc attacking a client pet owner - AddToHateList(hater, 1); - SetTarget(hater); - SetAttackingFlag(); - if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - - GetPet()->AddToHateList(hater, 1); - GetPet()->SetTarget(hater); - } - - m_auto_defend_timer.Disable(); - - return; - } - } - } - } - } + if (TryAutoDefend(bot_owner, leash_distance) ) { + return; } -//#pragma endregion - SetTarget(nullptr); if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { @@ -3592,313 +2199,1035 @@ void Bot::AI_Process() GetPet()->SetTarget(nullptr); } - if (GetPlayerState() & static_cast(PlayerState::Aggressive)) { + if (m_PlayerState & static_cast(PlayerState::Aggressive)) { SendRemovePlayerState(PlayerState::Aggressive); } -//#pragma region OK TO IDLE +// OK TO IDLE // Ok to idle - if ((NOT_GUARDING && fm_distance <= GetFollowDistance()) || (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())) { - - if (!IsMoving() && AI_think_timer->Check() && !spellend_timer.Enabled()) { - - if (NOT_PASSIVE) { - - if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) { - BotMeditate(true); - } - } - else { - - if (GetClass() != BARD) { - BotMeditate(true); - } - } - - return; - } - } - - // Non-engaged movement checks - if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == BARD)) { - - if (GUARDING) { - Goal = GetGuardPoint(); - } - else { - Goal = follow_mob->GetPosition(); - } - float destination_distance = DistanceSquared(GetPosition(), Goal); - - if ((!bot_owner->GetBotPulling() || PULLING_BOT) && (destination_distance > GetFollowDistance())) { - - if (!IsRooted()) { - - if (rest_timer.Enabled()) { - rest_timer.Disable(); - } - - bool running = true; - - if (destination_distance < GetFollowDistance() + BOT_FOLLOW_DISTANCE_WALK) { - running = false; - } - - if (running) { - RunTo(Goal.x, Goal.y, Goal.z); - } - else { - WalkTo(Goal.x, Goal.y, Goal.z); - } - - return; - } - } - else { - - if (IsMoving()) { - - StopMoving(); - return; - } - } - } - - // Basically, bard bots get a chance to cast idle spells while moving - if (GetClass() == BARD && IsMoving() && NOT_PASSIVE) { - - if (!spellend_timer.Enabled() && AI_think_timer->Check()) { - - AI_IdleCastCheck(); - return; - } - } - -//#pragma endregion - - } - -#undef TEST_COMBATANTS -#undef PULLING_BOT -#undef NOT_PULLING_BOT -#undef GUARDING -#undef NOT_GUARDING -#undef HOLDING -#undef NOT_HOLDING -#undef PASSIVE -#undef NOT_PASSIVE -} - -// AI Processing for a Bot object's pet -void Bot::PetAIProcess() { - if( !HasPet() || !GetPet() || !GetPet()->IsNPC()) - return; - - Mob* BotOwner = GetBotOwner(); - NPC* botPet = GetPet()->CastToNPC(); - if(!botPet->GetOwner() || !botPet->GetID() || !botPet->GetOwnerID()) { - Kill(); - return; - } - - if (!botPet->IsAIControlled() || botPet->GetAttackTimer().Check(false) || botPet->IsCasting() || !botPet->GetOwner()->IsBot()) - return; - - if (IsEngaged()) { - if (botPet->IsRooted()) - botPet->SetTarget(hate_list.GetClosestEntOnHateList(botPet)); - else - botPet->SetTarget(hate_list.GetEntWithMostHateOnList(botPet)); - - // Let's check if we have a los with our target. - // If we don't, our hate_list is wiped. - // It causes some cpu stress but without it, it was causing the bot/pet to aggro behind wall, floor etc... - if(!botPet->CheckLosFN(botPet->GetTarget()) || botPet->GetTarget()->IsMezzed() || !botPet->IsAttackAllowed(GetTarget())) { - botPet->WipeHateList(); - botPet->SetTarget(botPet->GetOwner()); + if (TryIdleChecks(fm_distance)) { return; } + if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { + return; + } + if (TryBardMovementCasts()) { + return; + } + } +} - botPet->FaceTarget(botPet->GetTarget()); - bool is_combat_range = botPet->CombatRange(botPet->GetTarget()); - // Ok, we're engaged, each class type has a special AI - // Only melee class will go to melee. Casters and healers will stay behind, following the leader by default. - // I should probably make the casters staying in place so they can cast.. +bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast idle spells while moving - // Ok, we 're a melee or any other class lvl<12. Yes, because after it becomes hard to go in melee for casters.. even for bots.. - if(is_combat_range) { - botPet->GetAIMovementTimer()->Check(); - if(botPet->IsMoving()) { - botPet->SetHeading(botPet->GetTarget()->GetHeading()); - if(moved) { - moved = false; - botPet->SetRunAnimSpeed(0); + if (GetClass() == BARD && IsMoving() && NOT_PASSIVE && !spellend_timer.Enabled() && AI_think_timer->Check()) { + + AI_IdleCastCheck(); + return true; + } + return false; +} + +bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks + + if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == BARD)) { + if (GUARDING) { + Goal = GetGuardPoint(); + } + else { + Goal = follow_mob->GetPosition(); + } + float destination_distance = DistanceSquared(GetPosition(), Goal); + + if ((!bot_owner->GetBotPulling() || PULLING_BOT) && (destination_distance > GetFollowDistance())) { + + if (!IsRooted()) { + + if (rest_timer.Enabled()) { + rest_timer.Disable(); } + + bool running = true; + + if (destination_distance < GetFollowDistance() + BOT_FOLLOW_DISTANCE_WALK) { + running = false; + } + + if (running) { + RunTo(Goal.x, Goal.y, Goal.z); + } + else { + WalkTo(Goal.x, Goal.y, Goal.z); + } + + return true; + } + } + else { + + if (IsMoving()) { + + StopMoving(); + return true; + } + } + } + return false; +} + +bool Bot::TryIdleChecks(float fm_distance) { + + if ( + ( + (NOT_GUARDING && fm_distance <= GetFollowDistance()) || + (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())) && + !IsMoving() && + AI_think_timer->Check() && + !spellend_timer.Enabled() + ) { + + if (NOT_PASSIVE) { + + if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) { + BotMeditate(true); } - if(!botPet->IsMoving()) { - float newX = 0; - float newY = 0; - float newZ = 0; - bool petHasAggro = false; - if(botPet->GetTarget() && botPet->GetTarget()->GetHateTop() && botPet->GetTarget()->GetHateTop() == botPet) - petHasAggro = true; - - if(botPet->GetClass() == ROGUE && !petHasAggro && !botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY())) { - // Move the rogue to behind the mob - if(botPet->PlotPositionAroundTarget(botPet->GetTarget(), newX, newY, newZ)) { - botPet->RunTo(newX, newY, newZ); - return; - } - } - else if(GetTarget() == botPet->GetTarget() && !petHasAggro && !botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY())) { - // If the bot owner and the bot are fighting the same mob, then move the pet to the rear arc of the mob - if(botPet->PlotPositionAroundTarget(botPet->GetTarget(), newX, newY, newZ)) { - botPet->RunTo(newX, newY, newZ); - return; - } - } - else if(DistanceSquaredNoZ(botPet->GetPosition(), botPet->GetTarget()->GetPosition()) < botPet->GetTarget()->GetSize()) { - // Let's try to adjust our melee range so we don't appear to be bunched up - bool isBehindMob = false; - bool moveBehindMob = false; - if(botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY())) - isBehindMob = true; - - if (!isBehindMob && !petHasAggro) - moveBehindMob = true; - - if(botPet->PlotPositionAroundTarget(botPet->GetTarget(), newX, newY, newZ, moveBehindMob)) { - botPet->RunTo(newX, newY, newZ); - return; - } - } - } - - // we can't fight if we don't have a target, are stun/mezzed or dead.. - if(botPet->GetTarget() && !botPet->IsStunned() && !botPet->IsMezzed() && (botPet->GetAppearance() != eaDead)) { - // check the delay on the attack - if(botPet->GetAttackTimer().Check()) { - // Stop attacking while we are on a front arc and the target is enraged - if(!botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY()) && botPet->GetTarget()->IsEnraged()) - return; - - if (botPet->Attack(GetTarget(), EQ::invslot::slotPrimary)) // try the main hand - if (botPet->GetTarget()) { - // We're a pet so we re able to dual attack - int32 RandRoll = zone->random.Int(0, 99); - if (botPet->CanThisClassDoubleAttack() && (RandRoll < (botPet->GetLevel() + NPCDualAttackModifier))) { - if (botPet->Attack(botPet->GetTarget(), EQ::invslot::slotPrimary)) {} - } - } - - if (botPet->GetOwner()->IsBot()) { - int aa_chance = 0; - int aa_skill = 0; - // Magician AA - aa_skill += botPet->GetOwner()->GetAA(aaElementalAlacrity); - // Necromancer AA - aa_skill += botPet->GetOwner()->GetAA(aaQuickeningofDeath); - // Beastlord AA - aa_skill += botPet->GetOwner()->GetAA(aaWardersAlacrity); - if(aa_skill >= 1) - aa_chance += ((aa_skill > 5 ? 5 : aa_skill) * 4); - - if(aa_skill >= 6) - aa_chance += ((aa_skill - 5 > 3 ? 3 : aa_skill - 5) * 7); - - if(aa_skill >= 9) - aa_chance += ((aa_skill - 8 > 3 ? 3 : aa_skill - 8) * 3); - - if(aa_skill >= 12) - aa_chance += ((aa_skill - 11) * 1); - - - //aa_chance += botPet->GetOwner()->GetAA(aaCompanionsAlacrity) * 3; - - if (zone->random.Int(1, 100) < aa_chance) - Flurry(nullptr); - } - - // Ok now, let's check pet's offhand. - if (botPet->GetAttackDWTimer().Check() && botPet->GetOwnerID() && botPet->GetOwner() && ((botPet->GetOwner()->GetClass() == MAGICIAN) || (botPet->GetOwner()->GetClass() == NECROMANCER) || (botPet->GetOwner()->GetClass() == SHADOWKNIGHT) || (botPet->GetOwner()->GetClass() == BEASTLORD))) { - if(botPet->GetOwner()->GetLevel() >= 24) { - float DualWieldProbability = ((botPet->GetSkill(EQ::skills::SkillDualWield) + botPet->GetLevel()) / 400.0f); - DualWieldProbability -= zone->random.Real(0, 1); - if(DualWieldProbability < 0) { - botPet->Attack(botPet->GetTarget(), EQ::invslot::slotSecondary); - if (botPet->CanThisClassDoubleAttack()) { - int32 RandRoll = zone->random.Int(0, 99); - if (RandRoll < (botPet->GetLevel() + 20)) - botPet->Attack(botPet->GetTarget(), EQ::invslot::slotSecondary); - } - } - } - } - if(!botPet->GetOwner()) - return; - - // Special attack - botPet->DoClassAttacks(botPet->GetTarget()); - } - // See if the pet can cast any spell - botPet->AI_EngagedCastCheck(); - } } else { - // Now, if we cannot reach our target - if (!botPet->HateSummon()) { - if(botPet->GetTarget() && botPet->AI_PursueCastCheck()) {} - else if (botPet->GetTarget() && botPet->GetAIMovementTimer()->Check()) { - botPet->SetRunAnimSpeed(0); - if(!botPet->IsRooted()) { - LogAIDetail("Pursuing [{}] while engaged", botPet->GetTarget()->GetCleanName()); - botPet->RunTo(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ()); - return; - } else { - botPet->SetHeading(botPet->GetTarget()->GetHeading()); - if(moved) { - moved = false; - StopNavigation(); - botPet->StopNavigation(); + if (GetClass() != BARD) { + BotMeditate(true); + } + + } + return true; + } + return false; +} + +// This is as close as I could get without modifying the aggro mechanics and making it an expensive process... +// 'class Client' doesn't make use of hate_list +bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) { + + if (RuleB(Bots, AllowOwnerOptionAutoDefend) && bot_owner->GetBotOption(Client::booAutoDefend)) { + + if (!m_auto_defend_timer.Enabled()) { + + m_auto_defend_timer.Start(zone->random.Int(250, 1250)); // random timer to simulate 'awareness' (cuts down on scanning overhead) + return true; + } + + if ( + m_auto_defend_timer.Check() && + bot_owner->GetAggroCount() && + NOT_HOLDING && + NOT_PASSIVE + ) { + auto xhaters = bot_owner->GetXTargetAutoMgr(); + + if (xhaters && !xhaters->empty()) { + for (auto hater_iter : xhaters->get_list()) { + if (!hater_iter.spawn_id) { + continue; + } + + if (bot_owner->GetBotPulling() && bot_owner->GetTarget() && hater_iter.spawn_id == bot_owner->GetTarget()->GetID()) { + continue; + } + + auto hater = entity_list.GetMob(hater_iter.spawn_id); + if (hater && !hater->IsMezzed() && DistanceSquared(hater->GetPosition(), bot_owner->GetPosition()) <= leash_distance) { + // This is roughly equivilent to npc attacking a client pet owner + AddToHateList(hater, 1); + SetTarget(hater); + SetAttackingFlag(); + + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + GetPet()->AddToHateList(hater, 1); + GetPet()->SetTarget(hater); } + + m_auto_defend_timer.Disable(); + + return true; } } } } - } else { - // Ok if we're not engaged, what's happening.. - if(botPet->GetTarget() != botPet->GetOwner()) - botPet->SetTarget(botPet->GetOwner()); + } + return false; +} - if(!IsMoving()) - botPet->AI_IdleCastCheck(); +bool Bot::TryMeditate() { - if(botPet->GetAIMovementTimer()->Check()) { - switch(pStandingPetOrder) { - case SPO_Follow: { - float dist = DistanceSquared(botPet->GetPosition(), botPet->GetTarget()->GetPosition()); - botPet->SetRunAnimSpeed(0); - if(dist > 184) { - botPet->RunTo(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ()); - return; - } else { - botPet->SetHeading(botPet->GetTarget()->GetHeading()); - if(moved) { - moved = false; - StopNavigation(); - botPet->StopNavigation(); - } - } - break; + if (!IsMoving() && !spellend_timer.Enabled()) { + if (GetTarget() && AI_EngagedCastCheck()) { + BotMeditate(false); + } else if (GetArchetype() == ARCHETYPE_CASTER) { + BotMeditate(true); + } + + if (!(GetPlayerState() & static_cast(PlayerState::Aggressive))) { + SendAddPlayerState(PlayerState::Aggressive); + } + return true; + } + return false; +} + +// This code actually gets processed when we are too far away from target and have not engaged yet +bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { + + if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == BARD)) { + if (GetTarget() && !IsRooted()) { + LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); + Goal = GetTarget()->GetPosition(); + if (DistanceSquared(m_Position, Goal) <= leash_distance) { + RunTo(Goal.x, Goal.y, Goal.z); + + } else { + WipeHateList(); + SetTarget(nullptr); + + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); } - case SPO_Sit: - botPet->SetAppearance(eaSitting); - break; - case SPO_Guard: - botPet->NextGuardPosition(); - break; } + return true; + + } else { + if (IsMoving()) { + StopMoving(); + } + + return false; + } + } + + if (GetTarget() && GetTarget()->IsFeared() && !spellend_timer.Enabled() && AI_think_timer->Check()) { + if (!IsFacingMob(GetTarget())) { + FaceTarget(GetTarget()); + } + + // This is a mob that is fleeing either because it has been feared or is low on hitpoints + AI_PursueCastCheck(); + + return true; + } + return false; +} + +bool Bot::TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item) { + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (attack_dw_timer.Check() && CanThisClassDualWield()) { + const EQ::ItemData* s_itemdata = nullptr; + + // Can only dual wield without a weapon if you're a monk + if (s_item || (GetClass() == MONK)) { + + if (s_item) { + s_itemdata = s_item->GetItem(); + } + + bool use_fist = true; + if (s_itemdata) { + use_fist = false; + } + + if (use_fist || !s_itemdata->IsType2HWeapon()) { + + float DualWieldProbability = 0.0f; + + int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); + DualWieldProbability = ((GetSkill(EQ::skills::SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max chance + + int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); + DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); + + float random = zone->random.Real(0, 1); + if (random < DualWieldProbability) { // Max 78% for DW chance + Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + TryCombatProcs(s_item, tar, EQ::invslot::slotSecondary); + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (CanThisClassDoubleAttack() && CheckBotDoubleAttack() && tar->GetHP() > -10) { + Attack(tar, EQ::invslot::slotSecondary); // Single attack with offhand + } + } + } + } + } + return true; +} + +bool Bot::TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item) { + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (attack_timer.Check()) { // Process primary weapon attacks + + Attack(tar, EQ::invslot::slotPrimary); + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + TriggerDefensiveProcs(tar, EQ::invslot::slotPrimary, false); + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + TryCombatProcs(p_item, tar, EQ::invslot::slotPrimary); + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (CanThisClassDoubleAttack()) { + + if (CheckBotDoubleAttack()) { + Attack(tar, EQ::invslot::slotPrimary, true); + } + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (GetSpecialAbility(SPECATK_TRIPLE) && CheckBotDoubleAttack(true)) { + + Attack(tar, EQ::invslot::slotPrimary, true); + } + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + // quad attack, does this belong here?? + if (GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) { + Attack(tar, EQ::invslot::slotPrimary, true); + } + } + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + // Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). + if (int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance)) { + + if (zone->random.Int(0, 100) < flurrychance) { + + MessageString(Chat::NPCFlurry, YOU_FLURRY); + Attack(tar, EQ::invslot::slotPrimary, false); + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + Attack(tar, EQ::invslot::slotPrimary, false); + } + } + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + auto ExtraAttackChanceBonus = + (spellbonuses.ExtraAttackChance[0] + itembonuses.ExtraAttackChance[0] + + aabonuses.ExtraAttackChance[0]); + + if ( + ExtraAttackChanceBonus && + p_item && + p_item->GetItem()->IsType2HWeapon() && + zone->random.Int(0, 100) < ExtraAttackChanceBonus + ) { + Attack(tar, EQ::invslot::slotPrimary, false); + } + } + return true; +} + +// We can't fight if we don't have a target, are stun/mezzed or dead.. +bool Bot::TryClassAttacks(Mob* tar) { + +// Stop attacking if the target is enraged + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY())) { + return false; + } + + // First, special attack per class (kick, backstab etc..) + DoClassAttacks(tar); + return true; +} + +bool Bot::TryRangedAttack(Mob* tar) { + + if (IsBotArcher() && ranged_timer.Check(false)) { + + if (!GetTarget() || GetAppearance() == eaDead) { return false; } + if (GetTarget()->GetHPRatio() <= 99.0f) { + BotRangedAttack(tar); + } + return true; + } + return false; +} + +bool Bot::TryFacingTarget(Mob* tar) { + + if (!IsSitting() && !IsFacingMob(tar)) { + FaceTarget(tar); + return true; + } + return false; +} + + +bool Bot::TryEvade(Mob* tar) { + + if ( + !IsRooted() && + HasTargetReflection() && + !tar->IsFeared() && + !tar->IsStunned() && + GetClass() == ROGUE && + m_evade_timer.Check(false) + ) { + int timer_duration = (HideReuseTime - GetSkillReuseTime(EQ::skills::SkillHide)) * 1000; + + if (timer_duration < 0) { + timer_duration = 0; + } + + m_evade_timer.Start(timer_duration); + if (zone->random.Int(0, 260) < (int) GetSkill(EQ::skills::SkillHide)) { + RogueEvade(tar); + } + return true; + } + + return false; +} + +void Bot::CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item) { + + atCombatRange= false; + p_item= GetBotItem(EQ::invslot::slotPrimary); + s_item= GetBotItem(EQ::invslot::slotSecondary); + bool behind_mob = false; + bool backstab_weapon = false; + if (GetClass() == ROGUE) { + + behind_mob = BehindMob(tar, GetX(), GetY()); // Can be separated for other future use + backstab_weapon = p_item && p_item->GetItemBackstabDamage(); + } + + // Calculate melee distances + float melee_distance_max = 0.0f; + float melee_distance = 0.0f; + + CalcMeleeDistances(tar, p_item, s_item, behind_mob, backstab_weapon, melee_distance_max, melee_distance); + + float caster_distance_max = GetBotCasterMaxRange(melee_distance_max); + + bool atArcheryRange = IsArcheryRange(tar); + + SetRangerCombatWeapon(atArcheryRange); + + bool stop_melee_level = GetLevel() >= GetStopMeleeLevel(); + if (IsBotArcher() && atArcheryRange) { + atCombatRange = true; + } + else if (caster_distance_max && tar_distance <= caster_distance_max && stop_melee_level) { + atCombatRange = true; + } + else if (tar_distance <= melee_distance) { + atCombatRange = true; + } +} + +void Bot::SetRangerCombatWeapon(bool atArcheryRange) { + + if (GetRangerAutoWeaponSelect()) { + bool changeWeapons = false; + + if (atArcheryRange && !IsBotArcher()) { + SetBotArcherySetting(true); + changeWeapons = true; + } + + else if (!atArcheryRange && IsBotArcher()) { + SetBotArcherySetting(false); + changeWeapons = true; + } + + if (changeWeapons) { + ChangeBotArcherWeapons(IsBotArcher()); + } + } +} + +void Bot::CalcMeleeDistances(const Mob* tar, const EQ::ItemInstance* const& p_item, const EQ::ItemInstance* const& s_item, bool behind_mob, bool backstab_weapon, float& melee_distance_max, float& melee_distance) const { + + float size_mod = GetSize(); + float other_size_mod = tar->GetSize(); + + // For races with a fixed size + if (GetRace() == RT_DRAGON || GetRace() == RT_WURM || GetRace() == RT_DRAGON_7) { + // size_mod = 60.0f; + } + + else if (size_mod < 6.0f) { + size_mod = 8.0f; + } + + // For races with a fixed size + if (tar->GetRace() == RT_DRAGON || tar->GetRace() == RT_WURM || tar->GetRace() == RT_DRAGON_7) { + other_size_mod = 60.0f; + } + + else if (other_size_mod < 6.0f) { + other_size_mod = 8.0f; + } + + if (other_size_mod > size_mod) { + size_mod = other_size_mod; + } + + if (size_mod > 29.0f) { + size_mod *= size_mod; + } + + else if (size_mod > 19.0f) { + size_mod *= (size_mod * 2.0f); + } + + else { + size_mod *= (size_mod * 4.0f); + } + + // Prevention of ridiculously sized hit boxes + if (size_mod > 10000.0f) { + size_mod = (size_mod / 7.0f); + } + + melee_distance_max = size_mod; + + switch (GetClass()) { + case WARRIOR: + case PALADIN: + case SHADOWKNIGHT: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.45f; + } + else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) { + melee_distance = melee_distance_max * 0.35f; + } + else { + melee_distance = melee_distance_max * 0.40f; + } + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.95f; + } + else { + melee_distance = melee_distance_max * 0.75f; + } + break; + case ROGUE: + if (behind_mob && backstab_weapon) { + if (p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.30f; + } + else { + melee_distance = melee_distance_max * 0.25f; + } + break; + } + // Fall-through + default: + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + melee_distance = melee_distance_max * 0.70f; + } + else { + melee_distance = melee_distance_max * 0.50f; + } + + break; + } +} + +bool Bot::IsValidTarget(Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat, Mob* tar, float tar_distance) { + + if (HOLDING || + !tar->IsNPC() || + tar->IsMezzed() || + lo_distance > leash_distance || + tar_distance > leash_distance || + (!GetAttackingFlag() && !CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) || // This is suppose to keep bots from attacking things behind walls + !IsAttackAllowed(tar) || + (bo_alt_combat && + (!GetAttackingFlag() && NOT_PULLING_BOT && !leash_owner->AutoAttackEnabled() && !tar->GetHateAmount(this) && !tar->GetHateAmount(leash_owner)) + ) + ) + { + // Normally, we wouldn't want to do this without class checks..but, too many issues can arise if we let enchanter animation pets run rampant + if (HasPet()) { + GetPet()->RemoveFromHateList(tar); + GetPet()->SetTarget(nullptr); + } + + RemoveFromHateList(tar); + SetTarget(nullptr); + + SetAttackFlag(false); + SetAttackingFlag(false); + + if (PULLING_BOT) { + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + if (GetPet()) { + GetPet()->SetPetOrder(m_previous_pet_order); + } + } + + if (IsMoving()) { + StopMoving(); + } + + return false; + } + return true; +} + +Mob* Bot::GetBotTarget(Client* bot_owner) { + + Mob* tar = GetTarget(); + if (!tar || PASSIVE) { + if (GetTarget()) { + SetTarget(nullptr); + } + + WipeHateList(); + SetAttackFlag(false); + SetAttackingFlag(false); + if (PULLING_BOT) { + + // 'Flags' should only be set on the bot that is pulling + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + if (GetPet()) { + GetPet()->SetPetOrder(m_previous_pet_order); + } + } + + if (GetArchetype() == ARCHETYPE_CASTER) { + BotMeditate(true); + } + } + return tar; +} + +void Bot::AcquireBotTarget(Group* bot_group, Raid* raid, Client* leash_owner, float leash_distance) {// Group roles can be expounded upon in the future + Mob* assist_mob = nullptr; + bool find_target = true; + + if (bot_group) { + assist_mob = entity_list.GetMob(bot_group->GetMainAssistName()); + } + else if (raid) { + assist_mob = raid->GetRaidMainAssistOneByName(GetName()); + } + + if (assist_mob) { + if (assist_mob->GetTarget()) { + if (assist_mob != this) { + if (GetTarget() != assist_mob->GetTarget()) { + SetTarget(assist_mob->GetTarget()); + } + + if ( + HasPet() && + ( + GetClass() != ENCHANTER || + GetPet()->GetPetType() != petAnimation || + GetAA(aaAnimationEmpathy) >= 2 + ) + ) { + // This artificially inflates pet's target aggro..but, less expensive than checking hate each AI process + GetPet()->AddToHateList(assist_mob->GetTarget(), 1); + GetPet()->SetTarget(assist_mob->GetTarget()); + } + } + + find_target = false; + } else if (assist_mob != this) { + if (GetTarget()) { + SetTarget(nullptr); + } + + if ( + HasPet() && + ( + GetClass() != ENCHANTER || + GetPet()->GetPetType() != petAnimation || + GetAA(aaAnimationEmpathy) >= 1 + ) + ) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + + find_target = false; + } + } + + if (find_target) { + if (IsRooted()) { + auto closest = hate_list.GetClosestEntOnHateList(this, true); + if (closest) { + SetTarget(closest); + } + } else { + // This will keep bots on target for now..but, future updates will allow for rooting/stunning + if (auto escaping = hate_list.GetEscapingEntOnHateList(leash_owner, leash_distance)) { + SetTarget(escaping); + } + + if (!GetTarget()) { + auto most_hate = hate_list.GetEntWithMostHateOnList(this, nullptr, true); + if (most_hate) { + SetTarget(most_hate); + } + } + } + } +} + +bool Bot::ReturningFlagChecks(Client* bot_owner, float fm_distance) {// Need to make it back to group before clearing return flag + + if (fm_distance <= GetFollowDistance()) { + + // Once we're back, clear blocking flags so everyone else can join in + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (GetPet()) { + GetPet()->SetPetOrder(m_previous_pet_order); + } + } + + // Need to keep puller out of combat until they reach their 'return to' destination + if (HasTargetReflection()) { + + SetTarget(nullptr); + WipeHateList(); + return false; + } + return true; +} + +bool Bot::PullingFlagChecks(Client* bot_owner) { + + if (!GetTarget()) { + + WipeHateList(); + SetTarget(nullptr); + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (GetPet()) { + GetPet()->SetPetOrder(m_previous_pet_order); + } + + return false; + } + else if (GetTarget()->GetHateList().size()) { + + WipeHateList(); + SetTarget(nullptr); + SetPullingFlag(false); + SetReturningFlag(); + + if (HasPet() && + (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + + if (GetPlayerState() & static_cast(PlayerState::Aggressive)) { + SendRemovePlayerState(PlayerState::Aggressive); + } + } + + return true; +} + +void Bot::SetBotTarget(Client* bot_owner, Raid* raid, Group* bot_group, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat) { + + if (bo_alt_combat && m_alt_combat_hate_timer.Check(false)) { + // Empty hate list - let's find some aggro + if (!IsEngaged() && NOT_HOLDING && NOT_PASSIVE && (!bot_owner->GetBotPulling() || NOT_PULLING_BOT)) { + SetLeashOwnerTarget(leash_owner, bot_owner, lo_distance, leash_distance); + } + + else if (bot_group) { + for (const auto& bg_member: bot_group->members) { + if (!bg_member) { + continue; + } + + auto bgm_target = bg_member->GetTarget(); + if (!bgm_target || !bgm_target->IsNPC()) { + continue; + } + SetBotGroupTarget(bot_owner, leash_owner, lo_distance, leash_distance, bg_member, bgm_target); + } + } + else if (raid) { + for (const auto& raid_member : raid->members) { + if (!raid_member.member) { + continue; + } + + auto rm_target = raid_member.member->GetTarget(); + if (!rm_target || !rm_target->IsNPC()) { + continue; + } + SetBotGroupTarget(bot_owner, leash_owner, lo_distance, leash_distance, raid_member.member, rm_target); + } + } + } +} + +void Bot::HealRotationChecks() { + + if (IsMyHealRotationSet()) { + if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { + m_member_of_heal_rotation->SetMemberIsCasting(this); + m_member_of_heal_rotation->UpdateTargetHealingStats(HealRotationTarget()); + AdvanceHealRotation(); + } + else { + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + AdvanceHealRotation(false); + } + } +} + +bool Bot::IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid) { + + if (!bot_owner || !bot_group && !raid || !IsAIControlled()) { + return false; + } + + if (bot_owner->IsDead()) { + SetTarget(nullptr); + SetBotOwner(nullptr); + return false; + } + return true; +} + +bool Bot::CheckIfCasting(float fm_distance) { + + if (IsCasting()) { + if (IsHealRotationMember() && + m_member_of_heal_rotation->CastingOverride() && + m_member_of_heal_rotation->CastingTarget() != nullptr && + m_member_of_heal_rotation->CastingReady() && + m_member_of_heal_rotation->CastingMember() == this && + !m_member_of_heal_rotation->MemberIsCasting(this)) + { + InterruptSpell(); + } + else if (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this) { + + AdvanceHealRotation(false); + return true; + } + else if (GetClass() != BARD) { + + if (IsEngaged()) { + return true; + } + + if ( + (NOT_GUARDING && fm_distance > GetFollowDistance()) || // Cancel out-of-combat casting if movement to follow mob is required + (GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) > GetFollowDistance()) // Cancel out-of-combat casting if movement to guard point is required + ) { + InterruptSpell(); + } + + return true; + } + } + else if (IsHealRotationMember()) { + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + } + return false; +} + +bool Bot::CheckIfIncapacitated() { + + if (GetPauseAI() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { + + if (IsCasting()) { + InterruptSpell(); + } + + if (IsMyHealRotationSet() || (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this)) { + AdvanceHealRotation(false); + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + } + return true; + } + + if (IsRooted() && IsMoving()) { + StopMoving(); + return true; + } + + return false; +} + +void Bot::SetBerserkState() {// Berserk updates should occur if primary AI criteria are met + if (GetClass() == WARRIOR || GetClass() == BERSERKER) { + + if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { + entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); + berserk = true; + } + + if (berserk && GetHPRatio() >= 30.0f) { + entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); + berserk = false; + } + } +} + +Mob* Bot::SetFollowMob(Client* leash_owner) { + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + if (!follow_mob) { + + follow_mob = leash_owner; + SetFollowID(leash_owner->GetID()); + } + return follow_mob; +} + +Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint32 r_group) const { + + Client* leash_owner = nullptr; + if (raid && r_group < MAX_RAID_GROUPS && raid->GetGroupLeader(r_group)) { + leash_owner = + raid->GetGroupLeader(r_group) && + raid->GetGroupLeader(r_group)->IsClient() ? + raid->GetGroupLeader(r_group)->CastToClient() : bot_owner; + + } else if (bot_group) { + bot_group->VerifyGroup(); + leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); + + } else { + leash_owner = bot_owner; + } + return leash_owner; +} + +void Bot::SetLeashOwnerTarget(Client* leash_owner, Client* bot_owner, float lo_distance, float leash_distance) { + + Mob* lo_target = leash_owner->GetTarget(); + if (lo_target && + lo_target->IsNPC() && + !lo_target->IsMezzed() && + ((bot_owner->GetBotOption(Client::booAutoDefend) && lo_target->GetHateAmount(leash_owner)) || leash_owner->AutoAttackEnabled()) && + lo_distance <= leash_distance && + DistanceSquared(m_Position, lo_target->GetPosition()) <= leash_distance && + (CheckLosFN(lo_target) || leash_owner->CheckLosFN(lo_target)) && + IsAttackAllowed(lo_target)) + { + AddToHateList(lo_target, 1); + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + GetPet()->AddToHateList(lo_target, 1); + GetPet()->SetTarget(lo_target); + } + } +} + +void Bot::SetOwnerTarget(Client* bot_owner) { + if (GetPet() && PULLING_BOT) { + GetPet()->SetPetOrder(m_previous_pet_order); + } + + SetAttackFlag(false); + SetAttackingFlag(false); + SetPullFlag(false); + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (NOT_HOLDING && NOT_PASSIVE) { + + auto attack_target = bot_owner->GetTarget(); + if (attack_target) { + + InterruptSpell(); + WipeHateList(); + AddToHateList(attack_target, 1); + SetTarget(attack_target); + SetAttackingFlag(); + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + + GetPet()->WipeHateList(); + GetPet()->AddToHateList(attack_target, 1); + GetPet()->SetTarget(attack_target); + } + } + } +} + +void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) { + SetAttackFlag(false); + SetAttackingFlag(false); + SetPullFlag(false); + SetPullingFlag(false); + SetReturningFlag(false); + bot_owner->SetBotPulling(false); + + if (NOT_HOLDING && NOT_PASSIVE) { + auto pull_target = bot_owner->GetTarget(); + if (pull_target) { + if (raid) { + const auto msg = fmt::format("Pulling {} to the group..", pull_target->GetCleanName()); + raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100); + } else { + BotGroupSay( + this, + fmt::format( + "Pulling {}.", + pull_target->GetCleanName() + ).c_str() + ); + } + + InterruptSpell(); + WipeHateList(); + AddToHateList(pull_target, 1); + SetTarget(pull_target); + SetPullingFlag(); + bot_owner->SetBotPulling(); + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + m_previous_pet_order = GetPet()->GetPetOrder(); + GetPet()->SetPetOrder(SPO_Guard); + } + } + } +} + +void Bot::SetBotGroupTarget(const Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, Mob* const& bg_member, Mob* bgm_target) { + if (!bgm_target->IsMezzed() && + ((bot_owner->GetBotOption(Client::booAutoDefend) && bgm_target->GetHateAmount(bg_member)) || leash_owner->AutoAttackEnabled()) && + lo_distance <= leash_distance && + DistanceSquared(m_Position, bgm_target->GetPosition()) <= leash_distance && + (CheckLosFN(bgm_target) || leash_owner->CheckLosFN(bgm_target)) && + IsAttackAllowed(bgm_target)) + { + AddToHateList(bgm_target, 1); + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { + GetPet()->AddToHateList(bgm_target, 1); + GetPet()->SetTarget(bgm_target); } } } @@ -3906,14 +3235,13 @@ void Bot::PetAIProcess() { void Bot::Depop() { WipeHateList(); entity_list.RemoveFromHateLists(this); - if(HasGroup()) - Bot::RemoveBotFromGroup(this, GetGroup()); - if(HasPet()) + if (HasPet()) GetPet()->Depop(); - _botOwner = 0; + _botOwner = nullptr; _botOwnerCharacterID = 0; + NPC::Depop(false); } @@ -3996,6 +3324,11 @@ bool Bot::Spawn(Client* botCharacterOwner) { } } + if (Raid* raid = entity_list.GetRaidByBotName(GetName())) + { + raid->VerifyRaid(); + SetRaidGrouped(true); + } return true; } @@ -4060,35 +3393,33 @@ uint32 Bot::GetBotItemBySlot(uint16 slot_id) return item_id; } - if (!database.botdb.LoadItemBySlot(GetBotID(), slot_id, item_id)) { - if (GetBotOwner() && GetBotOwner()->IsClient()) { - GetBotOwner()->CastToClient()->Message( - Chat::White, - fmt::format( - "Failed to load slot ID {} for {}.", - slot_id, - GetCleanName() - ).c_str() - ); - } + if (!database.botdb.LoadItemBySlot(GetBotID(), slot_id, item_id) && GetBotOwner() && GetBotOwner()->IsClient()) { + GetBotOwner()->CastToClient()->Message( + Chat::White, + fmt::format( + "Failed to load slot ID {} for {}.", + slot_id, + GetCleanName() + ).c_str() + ); } return item_id; } void Bot::SetLevel(uint8 in_level, bool command) { - if(in_level > 0) + if (in_level > 0) Mob::SetLevel(in_level, command); } void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { - if(ns) { + if (ns) { Mob::FillSpawnStruct(ns, ForWho); ns->spawn.afk = 0; ns->spawn.lfg = 0; ns->spawn.anon = 0; ns->spawn.gm = 0; - if(IsInAGuild()) + if (IsInAGuild()) ns->spawn.guildID = GuildID(); else ns->spawn.guildID = 0xFFFFFFFF; // 0xFFFFFFFF = NO GUILD, 0 = Unknown Guild @@ -4109,13 +3440,11 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { strcpy(ns->spawn.suffix, GetSuffix().c_str()); const EQ::ItemData* item = nullptr; const EQ::ItemInstance* inst = nullptr; - uint32 spawnedbotid = 0; - spawnedbotid = GetBotID(); for (int i = EQ::textures::textureBegin; i < EQ::textures::weaponPrimary; i++) { inst = GetBotItem(i); if (inst) { item = inst->GetItem(); - if (item != 0) { + if (item != nullptr) { ns->spawn.equipment.Slot[i].Material = item->Material; ns->spawn.equipment.Slot[i].EliteModel = item->EliteMaterial; ns->spawn.equipment.Slot[i].HerosForgeModel = item->HerosForgeModel; @@ -4131,19 +3460,22 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { } inst = GetBotItem(EQ::invslot::slotPrimary); - if(inst) { + if (inst) { item = inst->GetItem(); + if(item) { if(strlen(item->IDFile) > 2) ns->spawn.equipment.Primary.Material = Strings::ToInt(&item->IDFile[2]); + ns->spawn.equipment_tint.Primary.Color = GetEquipmentColor(EQ::textures::weaponPrimary); } } inst = GetBotItem(EQ::invslot::slotSecondary); - if(inst) { + if (inst) { item = inst->GetItem(); + if(item) { if(strlen(item->IDFile) > 2) ns->spawn.equipment.Secondary.Material = Strings::ToInt(&item->IDFile[2]); @@ -4215,9 +3547,12 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) { } auto spawned_bot_count_class = bot_class_spawned_count[b->GetClass() - 1]; - auto bot_spawn_limit_class = bot_class_spawn_limits[b->GetClass() - 1]; - if (bot_spawn_limit_class >= 0 && spawned_bot_count_class >= bot_spawn_limit_class) { + if ( + auto bot_spawn_limit_class = bot_class_spawn_limits[b->GetClass() - 1]; + bot_spawn_limit_class >= 0 && + spawned_bot_count_class >= bot_spawn_limit_class + ) { database.SetGroupID(b->GetCleanName(), 0, b->GetBotID()); g->UpdatePlayer(bot_owner); continue; @@ -4259,12 +3594,12 @@ void Bot::LoadAndSpawnAllZonedBots(Client* bot_owner) { // Returns TRUE if there is atleast 1 bot in the specified group bool Bot::GroupHasBot(Group* group) { bool Result = false; - if(group) { - for(int Counter = 0; Counter < MAX_GROUP_MEMBERS; Counter++) { + if (group) { + for (int Counter = 0; Counter < MAX_GROUP_MEMBERS; Counter++) { if (group->members[Counter] == nullptr) continue; - if(group->members[Counter]->IsBot()) { + if (group->members[Counter]->IsBot()) { Result = true; break; } @@ -4287,15 +3622,15 @@ uint32 Bot::SpawnedBotCount(const uint32 owner_id, uint8 class_id) { void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) { // This essentially performs a '#bot update,' with appearance packets, based on the current methods. // This should not be called outside of Client::SetEXP() due to it's lack of rule checks. - if(client) { + if (client) { std::list blist = entity_list.GetBotsByBotOwnerCharacterID(client->CharacterID()); - for(std::list::iterator biter = blist.begin(); biter != blist.end(); ++biter) { + for (auto biter = blist.begin(); biter != blist.end(); ++biter) { Bot* bot = *biter; - if(bot && (bot->GetLevel() != client->GetLevel())) { + if (bot && (bot->GetLevel() != client->GetLevel())) { bot->SetPetChooser(false); // not sure what this does, but was in bot 'update' code bot->CalcBotStats(client->GetBotOption(Client::booStatsUpdate)); - if(sendlvlapp) + if (sendlvlapp) bot->SendLevelAppearance(); // modified from Client::SetLevel() bot->SendAppearancePacket(AT_WhoLevel, level, true, true); // who level change @@ -4307,8 +3642,8 @@ void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) { } void Bot::SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 color) { - EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); - WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; + auto outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + auto wc = (WearChange_Struct*)outapp->pBuffer; wc->spawn_id = GetID(); wc->material = material; @@ -4334,9 +3669,8 @@ void Bot::BotAddEquipItem(uint16 slot_id, uint32 item_id) { // this is being called before bot is assigned an entity id.. // ..causing packets to be sent out to zone with an id of '0' if (item_id) { - uint8 material_from_slot = EQ::InventoryProfile::CalcMaterialFromSlot(slot_id); - if (material_from_slot != EQ::textures::materialInvalid) { + if (uint8 material_from_slot = EQ::InventoryProfile::CalcMaterialFromSlot(slot_id); material_from_slot != EQ::textures::materialInvalid) { equipment[slot_id] = item_id; // npc has more than just material slots. Valid material should mean valid inventory index if (GetID()) { // temp hack fix SendWearChange(material_from_slot); @@ -4344,10 +3678,8 @@ void Bot::BotAddEquipItem(uint16 slot_id, uint32 item_id) { } UpdateEquipmentLight(); - if (UpdateActiveLight()) { - if (GetID()) { // temp hack fix - SendAppearancePacket(AT_Light, GetActiveLightType()); - } + if (UpdateActiveLight() && GetID()) { // temp hack fix + SendAppearancePacket(AT_Light, GetActiveLightType()); } } } @@ -4507,46 +3839,47 @@ void Bot::RemoveBotItem(uint32 item_id) { bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) { bool Result = false; - if(bot && group) { - if(bot->HasGroup()) { - if(!group->IsLeader(bot)) { - bot->SetFollowID(0); - if(group->DelMember(bot)) - database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); - } else { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(!group->members[i]) - continue; - - group->members[i]->SetFollowID(0); - } - group->DisbandGroup(); + if (bot && group && bot->HasGroup()) { + if (!group->IsLeader(bot)) { + bot->SetFollowID(0); + if (group->DelMember(bot)) { database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); + if (group->GroupCount() < 1) { + group->DisbandGroup(); + } } - Result = true; + } else { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (!group->members[i]) { + continue; + } + + group->members[i]->SetFollowID(0); + } + group->DisbandGroup(); + database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); } + Result = true; } return Result; } bool Bot::AddBotToGroup(Bot* bot, Group* group) { bool Result = false; - if(bot && group) { - if(!bot->HasGroup()) { - // Add bot to this group - if(group->AddMember(bot)) { - if(group->GetLeader()) { - bot->SetFollowID(group->GetLeader()->GetID()); - // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader - if(group->GroupCount() == 2 && group->GetLeader()->IsClient()) { - group->UpdateGroupAAs(); - Mob *TempLeader = group->GetLeader(); - group->SendUpdate(groupActUpdate, TempLeader); - } + if (bot && group) { + // Add bot to this group + if (group->AddMember(bot)) { + if (group->GetLeader()) { + bot->SetFollowID(group->GetLeader()->GetID()); + // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader + if (group->GroupCount() == 2 && group->GetLeader()->IsClient()) { + group->UpdateGroupAAs(); + Mob *TempLeader = group->GetLeader(); + group->SendUpdate(groupActUpdate, TempLeader); } - group->VerifyGroup(); - Result = true; } + group->VerifyGroup(); + Result = true; } } return Result; @@ -5175,7 +4508,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* // Accept Items from Cursor to support bot command ^inventorygive if (begin_slot_id == invslot::slotCursor && end_slot_id == invslot::slotCursor) { EQ::ItemInstance* insts[1] = { 0 }; - EQ::InventoryProfile& user_inv = client->GetInv(); + user_inv = client->GetInv(); insts[0] = user_inv.GetItem(invslot::slotCursor); client->DeleteItemInInventory(invslot::slotCursor); @@ -5192,7 +4525,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* } else { EQ::ItemInstance* insts[8] = { 0 }; - EQ::InventoryProfile& user_inv = client->GetInv(); + user_inv = client->GetInv(); for (int i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; ++i) { insts[i - EQ::invslot::TRADE_BEGIN] = user_inv.GetItem(i); client->DeleteItemInInventory(i); @@ -5213,7 +4546,7 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* } bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) { - if(!NPC::Death(killerMob, damage, spell_id, attack_skill)) + if (!NPC::Death(killerMob, damage, spell_id, attack_skill)) return false; Save(); @@ -5228,26 +4561,26 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill Mob *give_exp = hate_list.GetDamageTopOnHateList(this); Client *give_exp_client = nullptr; - if(give_exp && give_exp->IsClient()) + if (give_exp && give_exp->IsClient()) give_exp_client = give_exp->CastToClient(); bool IsLdonTreasure = (GetClass() == LDON_TREASURE); - if(entity_list.GetCorpseByID(GetID())) + if (entity_list.GetCorpseByID(GetID())) entity_list.GetCorpseByID(GetID())->Depop(); Group *g = GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i]) { - if(g->members[i] == this) { + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i]) { + if (g->members[i] == this) { // If the leader dies, make the next bot the leader // and reset all bots followid - if(g->IsLeader(g->members[i])) { - if(g->members[i + 1]) { + if (g->IsLeader(g->members[i])) { + if (g->members[i + 1]) { g->SetLeader(g->members[i + 1]); g->members[i + 1]->SetFollowID(g->members[i]->GetFollowID()); - for(int j = 0; j < MAX_GROUP_MEMBERS; j++) { - if(g->members[j] && (g->members[j] != g->members[i + 1])) + for (int j = 0; j < MAX_GROUP_MEMBERS; j++) { + if (g->members[j] && (g->members[j] != g->members[i + 1])) g->members[j]->SetFollowID(g->members[i + 1]->GetID()); } } @@ -5263,8 +4596,8 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill // if group members exist below this one, move // them all up one slot in the group list int j = (i + 1); - for(; j < MAX_GROUP_MEMBERS; j++) { - if(g->members[j]) { + for (; j < MAX_GROUP_MEMBERS; j++) { + if (g->members[j]) { g->members[j-1] = g->members[j]; strcpy(g->membername[j-1], g->members[j]->GetCleanName()); g->membername[j][0] = '\0'; @@ -5278,9 +4611,9 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill GroupJoin_Struct* gu = (GroupJoin_Struct*)outapp->pBuffer; gu->action = groupActLeave; strcpy(gu->membername, GetCleanName()); - if(g) { - for(int k = 0; k < MAX_GROUP_MEMBERS; k++) { - if(g->members[k] && g->members[k]->IsClient()) + if (g) { + for (int k = 0; k < MAX_GROUP_MEMBERS; k++) { + if (g->members[k] && g->members[k]->IsClient()) g->members[k]->CastToClient()->QueuePacket(outapp); } } @@ -5296,6 +4629,20 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill my_owner->CastToClient()->SetBotPulling(false); } +Raid* raid = entity_list.GetRaidByBotName(GetName()); + + if (raid) + { + + for (int x = 0; x < MAX_RAID_MEMBERS; x++) + { + if (strcmp(raid->members[x].membername, GetName()) == 0) + { + raid->members[x].member = nullptr; + } + } + } + if (parse->BotHasQuestSub(EVENT_DEATH_COMPLETE)) { const auto& export_string = fmt::format( "{} {} {} {}", @@ -5309,11 +4656,12 @@ bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::Skill } entity_list.RemoveBot(GetID()); - return true; + +return true; } void Bot::Damage(Mob *from, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { - if(spell_id == 0) + if (spell_id == 0) spell_id = SPELL_UNKNOWN; //handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds @@ -5335,22 +4683,22 @@ void Bot::Damage(Mob *from, int64 damage, uint16 spell_id, EQ::skills::SkillType } CommonDamage(from, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic, special); - if(GetHP() < 0) { - if(IsCasting()) + if (GetHP() < 0) { + if (IsCasting()) InterruptSpell(); SetAppearance(eaDead); } SendHPUpdate(); - if(this == from) + if (this == from) return; // Aggro the bot's group members - if(IsGrouped()) { + if (IsGrouped()) { Group *g = GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && g->members[i]->IsBot() && from && !g->members[i]->CheckAggro(from) && g->members[i]->IsAttackAllowed(from)) + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i] && g->members[i]->IsBot() && from && !g->members[i]->CheckAggro(from) && g->members[i]->IsAttackAllowed(from)) g->members[i]->AddToHateList(from, 1); } } @@ -5403,17 +4751,17 @@ int Bot::GetHandToHandDamage(void) { } static uint8 mnk_dmg[] = {99, - 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, // 1-10 - 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, // 11-20 - 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, // 21-30 + 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, // 1-10 + 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, // 11-20 + 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, // 21-30 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, // 31-40 12, 12, 12, 12, 13, 13, 13, 13, 13, 14, // 41-50 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, // 51-60 - 14, 14}; // 61-62 + 14, 14}; // 61-62 static uint8 bst_dmg[] = {99, - 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, // 1-10 - 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, // 11-20 - 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, // 21-30 + 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, // 1-10 + 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, // 11-20 + 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, // 21-30 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, // 31-40 10, 11, 11, 11, 11, 11, 11, 12, 12}; // 41-49 if (GetClass() == MONK) { @@ -5461,13 +4809,13 @@ void Bot::DoRiposte(Mob* defender) { defender->Attack(this, EQ::invslot::slotPrimary, true); int32 DoubleRipChance = (defender->GetAABonuses().GiveDoubleRiposte[0] + defender->GetSpellBonuses().GiveDoubleRiposte[0] + defender->GetItemBonuses().GiveDoubleRiposte[0]); - if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { + if (DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { LogCombatDetail("Preforming a double riposte ([{}] percent chance)", DoubleRipChance); defender->Attack(this, EQ::invslot::slotPrimary, true); } DoubleRipChance = defender->GetAABonuses().GiveDoubleRiposte[1]; - if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { + if (DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { if (defender->GetClass() == MONK) defender->MonkSpecialAttack(this, defender->GetAABonuses().GiveDoubleRiposte[2]); else if (defender->IsBot()) @@ -5563,16 +4911,16 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) void Bot::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, bool HitChance) { int32 hate = max_damage; - if(hate_override > -1) + if (hate_override > -1) hate = hate_override; if (skill == EQ::skills::SkillBash) { const EQ::ItemInstance* inst = GetBotItem(EQ::invslot::slotSecondary); const EQ::ItemData* botweapon = nullptr; - if(inst) + if (inst) botweapon = inst->GetItem(); - if(botweapon) { + if (botweapon) { if (botweapon->ItemType == EQ::item::ItemTypeShield) hate += botweapon->AC; @@ -5599,22 +4947,12 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); - if(!GetTarget() || HasDied()) + if (!GetTarget() || HasDied()) return; if (my_hit.damage_done > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill - //if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){ - // int kb_chance = 25; - // kb_chance += (kb_chance * (100 - aabonuses.SpecialAttackKBProc[0]) / 100); - - // if (zone->random.Int(0, 99) < kb_chance) - // SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff); - // //who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC. - //} - if (HasSkillProcs()) TrySkillProc(who, skill, (ReuseTime * 1000)); @@ -5623,14 +4961,14 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max } void Bot::TryBackstab(Mob *other, int ReuseTime) { - if(!other) + if (!other) return; bool bIsBehind = false; bool bCanFrontalBS = false; const EQ::ItemInstance* inst = GetBotItem(EQ::invslot::slotPrimary); const EQ::ItemData* botpiercer = nullptr; - if(inst) + if (inst) botpiercer = inst->GetItem(); if (!botpiercer || (botpiercer->ItemType != EQ::item::ItemType1HPiercing)) { @@ -5649,15 +4987,15 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { if (bIsBehind || bCanFrontalBS) { int chance = (10 + (GetDEX() / 10) + (itembonuses.HeroicDEX / 10)); - if(level >= 60 && other->GetLevel() <= 45 && !other->CastToNPC()->IsEngaged() && other->GetHP()<= 32000 && other->IsNPC() && zone->random.Real(0, 99) < chance) { + if (level >= 60 && other->GetLevel() <= 45 && !other->CastToNPC()->IsEngaged() && other->GetHP()<= 32000 && other->IsNPC() && zone->random.Real(0, 99) < chance) { entity_list.MessageCloseString(this, false, 200, Chat::MeleeCrit, ASSASSINATES, GetName()); RogueAssassinate(other); } else { RogueBackstab(other); if (level > 54) { float DoubleAttackProbability = ((GetSkill(EQ::skills::SkillDoubleAttack) + GetLevel()) / 500.0f); - if(zone->random.Real(0, 1) < DoubleAttackProbability) { - if(other->GetHP() > 0) + if (zone->random.Real(0, 1) < DoubleAttackProbability) { + if (other->GetHP() > 0) RogueBackstab(other,false,ReuseTime); if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) @@ -5665,7 +5003,7 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { } } } - } else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { + } else if (aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { m_specialattacks = eSpecialAttacks::ChaoticStab; RogueBackstab(other, true); m_specialattacks = eSpecialAttacks::None; @@ -5698,8 +5036,8 @@ void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime) void Bot::RogueAssassinate(Mob* other) { EQ::ItemInstance* botweaponInst = GetBotItem(EQ::invslot::slotPrimary); - if(botweaponInst) { - if(GetWeaponDamage(other, botweaponInst)) + if (botweaponInst) { + if (GetWeaponDamage(other, botweaponInst)) other->Damage(this, 32000, SPELL_UNKNOWN, EQ::skills::SkillBackstab); else other->Damage(this, -5, SPELL_UNKNOWN, EQ::skills::SkillBackstab); @@ -5709,7 +5047,7 @@ void Bot::RogueAssassinate(Mob* other) { } void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { - if(!target || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) + if (!target || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) return; bool taunt_time = taunt_timer.Check(); @@ -5746,7 +5084,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { break; } case PALADIN: { - if(GetHPRatio() < 20) { + if (GetHPRatio() < 20) { CastSpell(SPELL_LAY_ON_HANDS, GetID()); knightattack_timer.Start(LayOnHandsReuseTime * 1000); } @@ -5762,8 +5100,8 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } } - if(taunting && target && target->IsNPC() && taunt_time) { - if(GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { + if (taunting && target && target->IsNPC() && taunt_time) { + if (GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { BotGroupSay( this, fmt::format( @@ -5829,7 +5167,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { break; } default: - break;; + break; } } @@ -5849,7 +5187,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1)) canBash = true; - if(!canBash || zone->random.Int(0, 100) > 25) + if (!canBash || zone->random.Int(0, 100) > 25) skill_to_use = EQ::skills::SkillKick; else skill_to_use = EQ::skills::SkillBash; @@ -5892,7 +5230,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { break; } - if(skill_to_use == -1) + if (skill_to_use == -1) return; int64 dmg = GetBaseSkillDamage(static_cast(skill_to_use), GetTarget()); @@ -5925,7 +5263,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { } if (skill_to_use == EQ::skills::SkillKick) { - if(target != this) { + if (target != this) { DoAnim(animKick); if (GetWeaponDamage(target, GetBotItem(EQ::invslot::slotFeet)) <= 0) dmg = DMG_INVULNERABLE; @@ -6011,7 +5349,7 @@ void Bot::AI_Stop() { } FACTION_VALUE Bot::GetReverseFactionCon(Mob* iOther) { - if(iOther->IsBot()) + if (iOther->IsBot()) return FACTION_ALLY; return NPC::GetReverseFactionCon(iOther); @@ -6019,7 +5357,7 @@ FACTION_VALUE Bot::GetReverseFactionCon(Mob* iOther) { Mob* Bot::GetOwnerOrSelf() { Mob* Result = nullptr; - if(GetBotOwner()) + if (GetBotOwner()) Result = GetBotOwner(); else Result = this; @@ -6030,7 +5368,7 @@ Mob* Bot::GetOwnerOrSelf() { Mob* Bot::GetOwner() { Mob* Result = nullptr; Result = GetBotOwner(); - if(!Result) + if (!Result) SetBotOwner(0); return Result; @@ -6038,42 +5376,42 @@ Mob* Bot::GetOwner() { bool Bot::IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined) { bool Result = false; - if(attacker && target) { - if(attacker == target) { + if (attacker && target) { + if (attacker == target) { hasRuleDefined = true; Result = false; - } else if(attacker->IsClient() && target->IsBot() && attacker->CastToClient()->GetPVP() && target->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { + } else if (attacker->IsClient() && target->IsBot() && attacker->CastToClient()->GetPVP() && target->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { hasRuleDefined = true; Result = true; - } else if(attacker->IsClient() && target->IsBot()) { + } else if (attacker->IsClient() && target->IsBot()) { hasRuleDefined = true; Result = false; - } else if(attacker->IsBot() && target->IsNPC()) { + } else if (attacker->IsBot() && target->IsNPC()) { hasRuleDefined = true; Result = true; - } else if(attacker->IsBot() && !target->IsNPC()) { + } else if (attacker->IsBot() && !target->IsNPC()) { hasRuleDefined = true; Result = false; - } else if(attacker->IsPet() && attacker->IsFamiliar()) { + } else if (attacker->IsPet() && attacker->IsFamiliar()) { hasRuleDefined = true; Result = false; - } else if(attacker->IsBot() && attacker->CastToBot()->GetBotOwner() && attacker->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { - if(target->IsBot() && target->GetOwner() && target->GetOwner()->CastToClient()->GetPVP()) { + } else if (attacker->IsBot() && attacker->CastToBot()->GetBotOwner() && attacker->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { + if (target->IsBot() && target->GetOwner() && target->GetOwner()->CastToClient()->GetPVP()) { hasRuleDefined = true; - if(target->GetOwner() == attacker->GetOwner()) + if (target->GetOwner() == attacker->GetOwner()) Result = false; else Result = true; - } else if(target->IsClient() && target->CastToClient()->GetPVP()) { + } else if (target->IsClient() && target->CastToClient()->GetPVP()) { hasRuleDefined = true; - if(target == attacker->GetOwner()) + if (target == attacker->GetOwner()) Result = false; else Result = true; - } else if(target->IsNPC()) { + } else if (target->IsNPC()) { hasRuleDefined = true; Result = true; - } else if(!target->IsNPC()) { + } else if (!target->IsNPC()) { hasRuleDefined = true; Result = false; } @@ -6104,23 +5442,21 @@ void Bot::BotOrderCampAll(Client* c, uint8 class_id) { const auto& l = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); for (const auto& b : l) { if (!class_id || b->GetClass() == class_id) { - b->Camp(); + b->Camp(true); } } } } void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) { - if(botOwner) { - if(botOwner->IsClient()) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CastToClient()->CharacterID()); - if(!BotList.empty()) { - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot) { - tempBot->SetTarget(0); - tempBot->SetBotOwner(0); - } + if (botOwner && botOwner->IsClient()) { + std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CastToClient()->CharacterID()); + if (!BotList.empty()) { + for (std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { + Bot* tempBot = *botListItr; + if (tempBot) { + tempBot->SetTarget(nullptr); + tempBot->SetBotOwner(nullptr); } } } @@ -6148,9 +5484,9 @@ int64 Bot::CalcMaxMana() { } } - if(current_mana > max_mana) + if (current_mana > max_mana) current_mana = max_mana; - else if(max_mana < 0) + else if (max_mana < 0) max_mana = 0; return max_mana; @@ -6218,7 +5554,7 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { int64 tic_inc = 0; tic_inc = GetFocusEffect(focusSpellDurByTic, spell_id); - if(IsBeneficialSpell(spell_id)) { + if (IsBeneficialSpell(spell_id)) { switch (GetAA(aaSpellCastingReinforcement)) { case 1: increase += 5; @@ -6238,7 +5574,7 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { increase += 20; } - if(IsMezSpell(spell_id)) + if (IsMezSpell(spell_id)) tic_inc += GetAA(aaMesmerizationMastery); return (((duration * increase) / 100) + tic_inc); @@ -6247,13 +5583,13 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { float Bot::GetAOERange(uint16 spell_id) { float range; range = spells[spell_id].aoe_range; - if(range == 0) + if (range == 0) range = spells[spell_id].range; - if(range == 0) + if (range == 0) range = 10; - if(IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) { + if (IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) { float song_bonus = (aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange); range += (range * song_bonus / 100.0f); } @@ -6264,13 +5600,13 @@ float Bot::GetAOERange(uint16 spell_id) { bool Bot::SpellEffect(Mob* caster, uint16 spell_id, float partial) { bool Result = false; Result = Mob::SpellEffect(caster, spell_id, partial); - if(IsGrouped()) { + if (IsGrouped()) { Group *g = GetGroup(); - if(g) { + if (g) { EQApplicationPacket hp_app; CreateHPPacket(&hp_app); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && g->members[i]->IsClient()) + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i] && g->members[i]->IsClient()) g->members[i]->CastToClient()->QueuePacket(&hp_app); } } @@ -6411,21 +5747,21 @@ bool Bot::SpellOnTarget( bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { bool Result = false; - if(!caster) + if (!caster) return false; - if(!IsSacrificeSpell(spell_id) && !(zone->GetZoneID() == 202) && !(this == caster)) { + if (!IsSacrificeSpell(spell_id) && zone->GetZoneID() != Zones::POKNOWLEDGE && this != caster) { Result = Mob::IsImmuneToSpell(spell_id, caster); - if(!Result) { - if(caster->IsBot()) { - if(spells[spell_id].target_type == ST_Undead) { - if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Undead) && (GetBodyType() != BT_Vampire)) { + if (!Result) { + if (caster->IsBot()) { + if (spells[spell_id].target_type == ST_Undead) { + if ((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Undead) && (GetBodyType() != BT_Vampire)) { LogSpellsDetail("Bot's target is not an undead"); return true; } } - if(spells[spell_id].target_type == ST_Summoned) { - if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Summoned2) && (GetBodyType() != BT_Summoned3)) { + if (spells[spell_id].target_type == ST_Summoned) { + if ((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Summoned2) && (GetBodyType() != BT_Summoned3)) { LogSpellsDetail("Bot's target is not a summoned creature"); return true; } @@ -6442,8 +5778,8 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction, EQ::spells::CastingSlot slot) { bool Result = false; SpellTargetType targetType = spells[spell_id].target_type; - if(targetType == ST_GroupClientAndPet) { - if((spell_id == 1768 && zone->GetZoneID() == 202) || (!IsDetrimentalSpell(spell_id))) { + if (targetType == ST_GroupClientAndPet) { + if ((spell_id == 1768 && zone->GetZoneID() == 202) || (!IsDetrimentalSpell(spell_id))) { CastAction = SingleTarget; return true; } @@ -6454,12 +5790,12 @@ bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce bool Bot::DoCastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, uint32 aa_id) { bool Result = false; - if(GetClass() == BARD) + if (GetClass() == BARD) cast_time = 0; Result = Mob::DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot, aa_id); - if(oSpellWillFinish) { + if (oSpellWillFinish) { const SPDat_Spell_Struct &spell = spells[spell_id]; *oSpellWillFinish = Timer::GetCurrentTime() + ((spell.recast_time > 20000) ? 10000 : spell.recast_time); } @@ -6477,18 +5813,18 @@ int32 Bot::GenerateBaseManaPoints() { case 'I': WisInt = INT; if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { - if(WisInt > 100) { + if (WisInt > 100) { ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if(WisInt > 201) + if (WisInt > 201) ConvertedWisInt -= ((WisInt - 201) * 5 / 4); } else ConvertedWisInt = WisInt; - if(GetLevel() < 41) { + if (GetLevel() < 41) { wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000); base_mana = (GetLevel() * 15); - } else if(GetLevel() < 81) { + } else if (GetLevel() < 81) { wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100)); base_mana = (600 + ((GetLevel() - 40) * 30)); } else { @@ -6497,13 +5833,13 @@ int32 Bot::GenerateBaseManaPoints() { } bot_mana = (base_mana + wisint_mana); } else { - if(((WisInt - 199) / 2) > 0) + if (((WisInt - 199) / 2) > 0) MindLesserFactor = ((WisInt - 199) / 2); else MindLesserFactor = 0; MindFactor = WisInt - MindLesserFactor; - if(WisInt > 100) + if (WisInt > 100) bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40); else bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100); @@ -6512,17 +5848,17 @@ int32 Bot::GenerateBaseManaPoints() { case 'W': WisInt = WIS; if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { - if(WisInt > 100) { + if (WisInt > 100) { ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if(WisInt > 201) + if (WisInt > 201) ConvertedWisInt -= ((WisInt - 201) * 5 / 4); } else ConvertedWisInt = WisInt; - if(GetLevel() < 41) { + if (GetLevel() < 41) { wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000); base_mana = (GetLevel() * 15); - } else if(GetLevel() < 81) { + } else if (GetLevel() < 81) { wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100)); base_mana = (600 + ((GetLevel() - 40) * 30)); } else { @@ -6531,13 +5867,13 @@ int32 Bot::GenerateBaseManaPoints() { } bot_mana = (base_mana + wisint_mana); } else { - if(((WisInt - 199) / 2) > 0) + if (((WisInt - 199) / 2) > 0) MindLesserFactor = ((WisInt - 199) / 2); else MindLesserFactor = 0; MindFactor = (WisInt - MindLesserFactor); - if(WisInt > 100) + if (WisInt > 100) bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40); else bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100); @@ -6552,13 +5888,13 @@ int32 Bot::GenerateBaseManaPoints() { } void Bot::GenerateSpecialAttacks() { - if(((GetClass() == MONK) || (GetClass() == WARRIOR) || (GetClass() == RANGER) || (GetClass() == BERSERKER)) && (GetLevel() >= 60)) + if (((GetClass() == MONK) || (GetClass() == WARRIOR) || (GetClass() == RANGER) || (GetClass() == BERSERKER)) && (GetLevel() >= 60)) SetSpecialAbility(SPECATK_TRIPLE, 1); } bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { - if(GetClass() == BARD) { - if(!ApplyBardPulse(bardsong, this, bardsong_slot)) + if (GetClass() == BARD) { + if (!ApplyBardPulse(bardsong, this, bardsong_slot)) InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); stopLogic = true; @@ -6567,59 +5903,68 @@ bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, EQ::spells: } bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { - if(spellTarget) { - if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) { - bool noGroupSpell = false; - uint16 thespell = spell_id; - for (int i = 0; i < AIBot_spells.size(); i++) { - int j = BotGetSpells(i); - int spelltype = BotGetSpellType(i); - bool spellequal = (j == thespell); - bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); - bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].target_type == ST_Self)); - bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == SHAMAN)); - bool slotequal = (slot == EQ::spells::CastingSlot::Item); - if(spellequal || slotequal) { - if((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { - if(((spells[thespell].effect_id[0] == 0) && (spells[thespell].base_value[0] < 0)) && - (spellTarget->GetHP() < ((spells[thespell].base_value[0] * (-1)) + 100))) { - LogSpells("GroupBuffing failure"); - return false; - } - SpellOnTarget(thespell, spellTarget); - noGroupSpell = true; - stopLogic = true; + if ( + spellTarget && + IsGrouped() && + ( + spellTarget->IsBot() || + spellTarget->IsClient() + ) && + RuleB(Bots, GroupBuffing) + ) { + bool noGroupSpell = false; + uint16 thespell = spell_id; + for (int i = 0; i < AIBot_spells.size(); i++) { + int j = BotGetSpells(i); + int spelltype = BotGetSpellType(i); + bool spellequal = (j == thespell); + bool spelltypeequal = ((spelltype == 2) || (spelltype == 16) || (spelltype == 32)); + bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].target_type == ST_Self)); + bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == SHAMAN)); + bool slotequal = (slot == EQ::spells::CastingSlot::Item); + if (spellequal || slotequal) { + if ((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { + if (((spells[thespell].effect_id[0] == 0) && (spells[thespell].base_value[0] < 0)) && + (spellTarget->GetHP() < ((spells[thespell].base_value[0] * (-1)) + 100))) { + LogSpells("GroupBuffing failure"); + return false; } + + SpellOnTarget(thespell, spellTarget); + noGroupSpell = true; + stopLogic = true; } } - - if(!noGroupSpell) { - Group *g = GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i]) { - if((g->members[i]->GetClass() == NECROMANCER) && (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { - } - else - SpellOnTarget(thespell, g->members[i]); - - if(g->members[i] && g->members[i]->GetPetID()) - SpellOnTarget(thespell, g->members[i]->GetPet()); - } - } - SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (g->GroupCount() - 1))); - } - } - stopLogic = true; } + + if (!noGroupSpell) { + Group *g = GetGroup(); + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i]) { + if ((g->members[i]->GetClass() == NECROMANCER) && (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { + } + else + SpellOnTarget(thespell, g->members[i]); + + if (g->members[i] && g->members[i]->GetPetID()) + SpellOnTarget(thespell, g->members[i]->GetPet()); + } + } + SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (g->GroupCount() - 1))); + } + } + stopLogic = true; } return true; } bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { bool isMainGroupMGB = false; - if(isMainGroupMGB && (GetClass() != BARD)) { + Raid* raid = entity_list.GetRaidByBotName(GetName()); + + if (isMainGroupMGB && (GetClass() != BARD)) { BotGroupSay( this, fmt::format( @@ -6629,13 +5974,26 @@ bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spel ); SpellOnTarget(spell_id, this); entity_list.AESpell(this, this, spell_id, true); - } else { + } + else if (raid) + { + std::vector raid_group_members = raid->GetRaidGroupMembers(raid->GetGroup(GetName())); + for (auto iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member) { + SpellOnTarget(spell_id, iter->member); + if (iter->member && iter->member->GetPetID()) + SpellOnTarget(spell_id, iter->member ->GetPet()); + } + } + } + else + { Group *g = GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; ++i) { - if(g->members[i]) { + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { + if (g->members[i]) { SpellOnTarget(spell_id, g->members[i]); - if(g->members[i] && g->members[i]->GetPetID()) + if (g->members[i] && g->members[i]->GetPetID()) SpellOnTarget(spell_id, g->members[i]->GetPet()); } } @@ -6678,7 +6036,6 @@ void Bot::CalcBonuses() { } int64 Bot::CalcHPRegenCap() { - int level = GetLevel(); int64 hpregen_cap = 0; hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25); hpregen_cap += (aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap); @@ -6716,7 +6073,7 @@ int32 Bot::GetMaxStat() { int32 Bot::GetMaxResist() { int level = GetLevel(); int32 base = 500; - if(level > 60) + if (level > 60) base += ((level - 60) * 5); return base; @@ -6776,15 +6133,15 @@ int32 Bot::GetMaxCorrup() { int32 Bot::CalcSTR() { int32 val = (STR + itembonuses.STR + spellbonuses.STR); int32 mod = aabonuses.STR; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; STR = (val + mod); - if(STR < 1) + if (STR < 1) STR = 1; int m = GetMaxSTR(); - if(STR > m) + if (STR > m) STR = m; return STR; @@ -6793,15 +6150,15 @@ int32 Bot::CalcSTR() { int32 Bot::CalcSTA() { int32 val = (STA + itembonuses.STA + spellbonuses.STA); int32 mod = aabonuses.STA; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; STA = (val + mod); - if(STA < 1) + if (STA < 1) STA = 1; int m = GetMaxSTA(); - if(STA > m) + if (STA > m) STA = m; return STA; @@ -6810,15 +6167,15 @@ int32 Bot::CalcSTA() { int32 Bot::CalcAGI() { int32 val = (AGI + itembonuses.AGI + spellbonuses.AGI); int32 mod = aabonuses.AGI; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; AGI = (val + mod); - if(AGI < 1) + if (AGI < 1) AGI = 1; int m = GetMaxAGI(); - if(AGI > m) + if (AGI > m) AGI = m; return AGI; @@ -6827,15 +6184,15 @@ int32 Bot::CalcAGI() { int32 Bot::CalcDEX() { int32 val = (DEX + itembonuses.DEX + spellbonuses.DEX); int32 mod = aabonuses.DEX; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; DEX = (val + mod); - if(DEX < 1) + if (DEX < 1) DEX = 1; int m = GetMaxDEX(); - if(DEX > m) + if (DEX > m) DEX = m; return DEX; @@ -6844,16 +6201,16 @@ int32 Bot::CalcDEX() { int32 Bot::CalcINT() { int32 val = (INT + itembonuses.INT + spellbonuses.INT); int32 mod = aabonuses.INT; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; INT = (val + mod); - if(INT < 1) + if (INT < 1) INT = 1; int m = GetMaxINT(); - if(INT > m) + if (INT > m) INT = m; return INT; @@ -6862,16 +6219,16 @@ int32 Bot::CalcINT() { int32 Bot::CalcWIS() { int32 val = (WIS + itembonuses.WIS + spellbonuses.WIS); int32 mod = aabonuses.WIS; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; WIS = (val + mod); - if(WIS < 1) + if (WIS < 1) WIS = 1; int m = GetMaxWIS(); - if(WIS > m) + if (WIS > m) WIS = m; return WIS; @@ -6880,16 +6237,16 @@ int32 Bot::CalcWIS() { int32 Bot::CalcCHA() { int32 val = (CHA + itembonuses.CHA + spellbonuses.CHA); int32 mod = aabonuses.CHA; - if(val > 255 && GetLevel() <= 60) + if (val > 255 && GetLevel() <= 60) val = 255; CHA = (val + mod); - if(CHA < 1) + if (CHA < 1) CHA = 1; int m = GetMaxCHA(); - if(CHA > m) + if (CHA > m) CHA = m; return CHA; @@ -6897,13 +6254,13 @@ int32 Bot::CalcCHA() { int32 Bot::CalcMR() { MR += (itembonuses.MR + spellbonuses.MR + aabonuses.MR); - if(GetClass() == WARRIOR) + if (GetClass() == WARRIOR) MR += (GetLevel() / 2); - if(MR < 1) + if (MR < 1) MR = 1; - if(MR > GetMaxMR()) + if (MR > GetMaxMR()) MR = GetMaxMR(); return MR; @@ -6911,19 +6268,19 @@ int32 Bot::CalcMR() { int32 Bot::CalcFR() { int c = GetClass(); - if(c == RANGER) { + if (c == RANGER) { FR += 4; int l = GetLevel(); - if(l > 49) + if (l > 49) FR += (l - 49); } FR += (itembonuses.FR + spellbonuses.FR + aabonuses.FR); - if(FR < 1) + if (FR < 1) FR = 1; - if(FR > GetMaxFR()) + if (FR > GetMaxFR()) FR = GetMaxFR(); return FR; @@ -6931,23 +6288,23 @@ int32 Bot::CalcFR() { int32 Bot::CalcDR() { int c = GetClass(); - if(c == PALADIN) { + if (c == PALADIN) { DR += 8; int l = GetLevel(); - if(l > 49) + if (l > 49) DR += (l - 49); - } else if(c == SHADOWKNIGHT) { + } else if (c == SHADOWKNIGHT) { DR += 4; int l = GetLevel(); - if(l > 49) + if (l > 49) DR += (l - 49); } DR += (itembonuses.DR + spellbonuses.DR + aabonuses.DR); - if(DR < 1) + if (DR < 1) DR = 1; - if(DR > GetMaxDR()) + if (DR > GetMaxDR()) DR = GetMaxDR(); return DR; @@ -6955,24 +6312,24 @@ int32 Bot::CalcDR() { int32 Bot::CalcPR() { int c = GetClass(); - if(c == ROGUE) { + if (c == ROGUE) { PR += 8; int l = GetLevel(); - if(l > 49) + if (l > 49) PR += (l - 49); - } else if(c == SHADOWKNIGHT) { + } else if (c == SHADOWKNIGHT) { PR += 4; int l = GetLevel(); - if(l > 49) + if (l > 49) PR += (l - 49); } PR += (itembonuses.PR + spellbonuses.PR + aabonuses.PR); - if(PR < 1) + if (PR < 1) PR = 1; - if(PR > GetMaxPR()) + if (PR > GetMaxPR()) PR = GetMaxPR(); return PR; @@ -6980,19 +6337,19 @@ int32 Bot::CalcPR() { int32 Bot::CalcCR() { int c = GetClass(); - if(c == RANGER) { + if (c == RANGER) { CR += 4; int l = GetLevel(); - if(l > 49) + if (l > 49) CR += (l - 49); } CR += (itembonuses.CR + spellbonuses.CR + aabonuses.CR); - if(CR < 1) + if (CR < 1) CR = 1; - if(CR > GetMaxCR()) + if (CR > GetMaxCR()) CR = GetMaxCR(); return CR; @@ -7000,7 +6357,7 @@ int32 Bot::CalcCR() { int32 Bot::CalcCorrup() { Corrup = (Corrup + itembonuses.Corrup + spellbonuses.Corrup + aabonuses.Corrup); - if(Corrup > GetMaxCorrup()) + if (Corrup > GetMaxCorrup()) Corrup = GetMaxCorrup(); return Corrup; @@ -7012,18 +6369,18 @@ int32 Bot::CalcATK() { } void Bot::CalcRestState() { - if(!RuleB(Character, RestRegenEnabled)) + if (!RuleB(Character, RestRegenEnabled)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; - if(IsEngaged() || !IsSitting() || !rest_timer.Check(false)) + if (IsEngaged() || !IsSitting() || !rest_timer.Check(false)) return; uint32 buff_count = GetMaxTotalSlots(); for (unsigned int j = 0; j < buff_count; j++) { - if(IsValidSpell(buffs[j].spellid)) { - if(IsDetrimentalSpell(buffs[j].spellid) && (buffs[j].ticsremaining > 0)) - if(!DetrimentalSpellAllowsRest(buffs[j].spellid)) + if (IsValidSpell(buffs[j].spellid)) { + if (IsDetrimentalSpell(buffs[j].spellid) && (buffs[j].ticsremaining > 0)) + if (!DetrimentalSpellAllowsRest(buffs[j].spellid)) return; } } @@ -7097,7 +6454,7 @@ int64 Bot::CalcManaRegen() { int32 regen = 0; if (IsSitting()) { BuffFadeBySitModifier(); - if(botclass != WARRIOR && botclass != MONK && botclass != ROGUE && botclass != BERSERKER) { + if (botclass != WARRIOR && botclass != MONK && botclass != ROGUE && botclass != BERSERKER) { regen = ((((GetSkill(EQ::skills::SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4); regen += (spellbonuses.ManaRegen + itembonuses.ManaRegen); } else @@ -7109,13 +6466,14 @@ int64 Bot::CalcManaRegen() { regen += itembonuses.HeroicINT * RuleR(Character, HeroicIntelligenceMultiplier) / 25; else if(GetCasterClass() == 'W') regen += itembonuses.HeroicWIS * RuleR(Character, HeroicWisdomMultiplier) / 25; + else regen = 0; regen += aabonuses.ManaRegen; regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); float mana_regen_rate = RuleR(Bots, ManaRegen); - if(mana_regen_rate < 0.0f) + if (mana_regen_rate < 0.0f) mana_regen_rate = 0.0f; regen = (regen * mana_regen_rate); @@ -7168,7 +6526,7 @@ int64 Bot::CalcMaxHP() { current_hp = max_hp; int hp_perc_cap = spellbonuses.HPPercCap[0]; - if(hp_perc_cap) { + if (hp_perc_cap) { int curHP_cap = ((max_hp * hp_perc_cap) / 100); if (current_hp > curHP_cap || (spellbonuses.HPPercCap[1] && current_hp > spellbonuses.HPPercCap[1])) current_hp = curHP_cap; @@ -7185,7 +6543,7 @@ int64 Bot::CalcMaxEndurance() { cur_end = max_end; int end_perc_cap = spellbonuses.EndPercCap[0]; - if(end_perc_cap) { + if (end_perc_cap) { int curEnd_cap = ((max_end * end_perc_cap) / 100); if (cur_end > curEnd_cap || (spellbonuses.EndPercCap[1] && cur_end > spellbonuses.EndPercCap[1])) cur_end = curEnd_cap; @@ -7230,6 +6588,7 @@ int64 Bot::CalcBaseEndurance() { } base_end = (base_endurance + sta_end + (heroic_stats * 10)); } else { + stats = (GetSTR() + GetSTA() + GetDEX() + GetAGI()); int level_base = (GetLevel() * 15); int at_most_800 = stats; @@ -7241,6 +6600,7 @@ int64 Bot::CalcBaseEndurance() { int Bonus800plus = 0; int HalfBonus800plus = 0; int BonusUpto800 = int(at_most_800 / 4) ; + if(stats > 400) { Bonus400to800 = int((at_most_800 - 400) / 4); HalfBonus400to800 = int(std::max((at_most_800 - 400), 0) / 8); @@ -7268,9 +6628,9 @@ int64 Bot::CalcEnduranceRegenCap() { } void Bot::SetEndurance(int32 newEnd) { - if(newEnd < 0) + if (newEnd < 0) newEnd = 0; - else if(newEnd > GetMaxEndurance()) + else if (newEnd > GetMaxEndurance()) newEnd = GetMaxEndurance(); cur_end = newEnd; @@ -7284,15 +6644,15 @@ void Bot::DoEnduranceUpkeep() { for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { if (IsValidSpell(buffs[buffs_i].spellid)) { int upkeep = spells[buffs[buffs_i].spellid].endurance_upkeep; - if(upkeep > 0) { - if(cost_redux > 0) { - if(upkeep <= cost_redux) + if (upkeep > 0) { + if (cost_redux > 0) { + if (upkeep <= cost_redux) continue; upkeep -= cost_redux; } - if((upkeep+upkeep_sum) > GetEndurance()) + if ((upkeep+upkeep_sum) > GetEndurance()) BuffFadeBySlot(buffs_i); else upkeep_sum += upkeep; @@ -7300,17 +6660,24 @@ void Bot::DoEnduranceUpkeep() { } } - if(upkeep_sum != 0) + if (upkeep_sum != 0) SetEndurance(GetEndurance() - upkeep_sum); } void Bot::Camp(bool save_to_database) { - Sit(); - if (GetGroup()) { - RemoveBotFromGroup(this, GetGroup()); + if (IsEngaged() || GetBotOwner()->IsEngaged()) { + GetBotOwner()->Message( + Chat::White, + fmt::format( + "You cannot camp your bots while in combat" + ).c_str() + ); + return; } + Sit(); + LeaveHealRotationMemberPool(); if (save_to_database) { @@ -7321,8 +6688,13 @@ void Bot::Camp(bool save_to_database) { } void Bot::Zone() { - if(HasGroup()) + Raid* raid = entity_list.GetRaidByBotName(GetName()); + if (raid) { + raid->MemberZoned(CastToClient()); + } + else if (HasGroup()) { GetGroup()->MemberZoned(this); + } Save(); Depop(); @@ -7330,12 +6702,12 @@ void Bot::Zone() { bool Bot::IsArcheryRange(Mob *target) { bool result = false; - if(target) { + if (target) { float range = (GetBotArcheryRange() + 5.0); range *= range; float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); float minRuleDistance = (RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist)); - if((targetDistance > range) || (targetDistance < minRuleDistance)) + if ((targetDistance > range) || (targetDistance < minRuleDistance)) result = false; else result = true; @@ -7488,34 +6860,13 @@ void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) doter->CastToBot()->SetGroupDoter(); } -//void Bot::UpdateRaidCastingRoles(const Raid* raid, bool disband = false) { } - -bool Bot::CanHeal() { - bool result = false; - - if(!AI_HasSpells()) - return false; - - BotSpell botSpell; - botSpell.SpellId = 0; - botSpell.SpellIndex = 0; - botSpell.ManaCost = 0; - - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); - - if(botSpell.SpellId != 0) - result = true; - - return result; -} - -Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { +Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName) { Bot* Result = nullptr; - if(c) { + if (c) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if(!BotList.empty()) { - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - if(std::string((*botListItr)->GetCleanName()) == botName) { + if (!BotList.empty()) { + for (auto botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { + if (std::string((*botListItr)->GetCleanName()) == botName) { Result = (*botListItr); break; } @@ -7525,14 +6876,14 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { return Result; } -void Bot::ProcessBotGroupInvite(Client* c, std::string botName) { - if(c) { +void Bot::ProcessBotGroupInvite(Client* c, std::string const& botName) { + if (c) { Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); - if(invitedBot && !invitedBot->HasGroup()) { - if(!c->IsGrouped()) { - Group *g = new Group(c); - if(AddBotToGroup(invitedBot, g)) { + if (invitedBot && !invitedBot->HasGroup() && !invitedBot->HasRaid()) { + if (!c->IsGrouped()) { + auto g = new Group(c); + if (AddBotToGroup(invitedBot, g)) { entity_list.AddGroup(g); database.SetGroupLeaderName(g->GetID(), c->GetName()); g->SaveGroupLeaderAA(); @@ -7545,16 +6896,24 @@ void Bot::ProcessBotGroupInvite(Client* c, std::string botName) { AddBotToGroup(invitedBot, c->GetGroup()); database.SetGroupID(invitedBot->GetCleanName(), c->GetGroup()->GetID(), invitedBot->GetBotID()); } + if (c->HasRaid() && c->HasGroup()) { + Raid* raid = entity_list.GetRaidByClient(c); + if (raid) { + raid->AddBot(invitedBot, raid->GetGroup(c), false, false, false); + } + } + } else if (invitedBot->HasGroup()) { + c->MessageString(Chat::LightGray, TARGET_ALREADY_IN_GROUP, invitedBot->GetCleanName()); } } } // Processes a group disband request from a Client for a Bot. -void Bot::ProcessBotGroupDisband(Client* c, std::string botName) { - if(c) { +void Bot::ProcessBotGroupDisband(Client* c, const std::string& botName) { + if (c) { Bot* tempBot = nullptr; - if(botName.empty()) + if (botName.empty()) tempBot = GetFirstBotInGroup(c->GetGroup()); else tempBot = GetBotByBotClientOwnerAndBotName(c, botName); @@ -7563,22 +6922,41 @@ void Bot::ProcessBotGroupDisband(Client* c, std::string botName) { } } +// Processes a raid disband request from a Client for a Bot. +void Bot::RemoveBotFromRaid(Bot* bot) { + + Raid* bot_raid = entity_list.GetRaidByBotName(bot->GetName()); + if (bot_raid) { + uint32 gid = bot_raid->GetGroup(bot->GetName()); + bot_raid->SendRaidGroupRemove(bot->GetName(), gid); + bot_raid->RemoveMember(bot->GetName()); + bot_raid->GroupUpdate(gid); + if (!bot_raid->RaidCount()) { + bot_raid->DisbandRaid(); + } + } +} + // Handles all client zone change event void Bot::ProcessClientZoneChange(Client* botOwner) { - if(botOwner) { + if (botOwner) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CharacterID()); - for(std::list::iterator itr = BotList.begin(); itr != BotList.end(); ++itr) { + for (std::list::iterator itr = BotList.begin(); itr != BotList.end(); ++itr) { Bot* tempBot = *itr; - if(tempBot) { - if(tempBot->HasGroup()) { + if (tempBot) { + Raid* raid = entity_list.GetRaidByBotName(tempBot->GetName()); + if (raid) { + tempBot->Zone(); + } + else if (tempBot->HasGroup()) { Group* g = tempBot->GetGroup(); - if(g && g->IsGroupMember(botOwner)) { - if(botOwner && botOwner->IsClient()) { + if (g && g->IsGroupMember(botOwner)) { + if (botOwner && botOwner->IsClient()) { // Modified to not only zone bots if you're the leader. // Also zone bots of the non-leader when they change zone. - if(tempBot->GetBotOwnerCharacterID() == botOwner->CharacterID() && g->IsGroupMember(botOwner)) + if (tempBot->GetBotOwnerCharacterID() == botOwner->CharacterID() && g->IsGroupMember(botOwner)) tempBot->Zone(); else tempBot->Camp(); @@ -7598,13 +6976,13 @@ void Bot::ProcessClientZoneChange(Client* botOwner) { Bot* Bot::GetFirstBotInGroup(Group* group) { Bot* Result = nullptr; - if(group) { - for(int Counter = 0; Counter < MAX_GROUP_MEMBERS; Counter++) { + if (group) { + for (int Counter = 0; Counter < MAX_GROUP_MEMBERS; Counter++) { if (group->members[Counter] == nullptr) { continue; } - if(group->members[Counter]->IsBot()) { + if (group->members[Counter]->IsBot()) { Result = group->members[Counter]->CastToBot(); break; } @@ -7616,7 +6994,7 @@ Bot* Bot::GetFirstBotInGroup(Group* group) { // Processes a client request to inspect a bot's equipment. void Bot::ProcessBotInspectionRequest(Bot* inspectedBot, Client* client) { - if(inspectedBot && client) { + if (inspectedBot && client) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_InspectAnswer, sizeof(InspectResponse_Struct)); InspectResponse_Struct* insr = (InspectResponse_Struct*) outapp->pBuffer; insr->TargetID = inspectedBot->GetNPCTypeID(); @@ -7628,9 +7006,9 @@ void Bot::ProcessBotInspectionRequest(Bot* inspectedBot, Client* client) { for (int16 L = EQ::invslot::EQUIPMENT_BEGIN; L <= EQ::invslot::EQUIPMENT_END; L++) { inst = inspectedBot->GetBotItem(L); - if(inst) { + if (inst) { item = inst->GetItem(); - if(item) { + if (item) { strcpy(insr->itemnames[L], item->Name); insr->itemicons[L] = item->Icon; } @@ -7658,19 +7036,19 @@ void Bot::CalcItemBonuses(StatBonuses* newbon) for (int i = EQ::invslot::BONUS_BEGIN; i <= EQ::invslot::BONUS_STAT_END; ++i) { const EQ::ItemInstance* item = GetBotItem(i); - if(item) { + if (item) { AddItemBonuses(item, newbon); } } // Caps - if(newbon->HPRegen > CalcHPRegenCap()) + if (newbon->HPRegen > CalcHPRegenCap()) newbon->HPRegen = CalcHPRegenCap(); - if(newbon->ManaRegen > CalcManaRegenCap()) + if (newbon->ManaRegen > CalcManaRegenCap()) newbon->ManaRegen = CalcManaRegenCap(); - if(newbon->EnduranceRegen > CalcEnduranceRegenCap()) + if (newbon->EnduranceRegen > CalcEnduranceRegenCap()) newbon->EnduranceRegen = CalcEnduranceRegenCap(); } @@ -7680,26 +7058,26 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool return; } - if(inst->GetAugmentType()==0 && isAug == true) + if (inst->GetAugmentType()==0 && isAug) { return; } const EQ::ItemData *item = inst->GetItem(); - if(!isTribute && !inst->IsEquipable(GetBaseRace(),GetClass())) + if (!isTribute && !inst->IsEquipable(GetBaseRace(),GetClass())) { if (item->ItemType != EQ::item::ItemTypeFood && item->ItemType != EQ::item::ItemTypeDrink) return; } - if(GetLevel() < inst->GetItemRequiredLevel(true)) + if (GetLevel() < inst->GetItemRequiredLevel(true)) { return; } auto rec_level = isAug ? rec_override : inst->GetItemRecommendedLevel(true); - if(GetLevel() >= rec_level) + if (GetLevel() >= rec_level) { newbon->AC += item->AC; newbon->HP += item->HP; @@ -7804,94 +7182,94 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool } //FatherNitwit: New style haste, shields, and regens - if(newbon->haste < (int32)item->Haste) { + if (newbon->haste < (int32)item->Haste) { newbon->haste = item->Haste; } - if(item->Regen > 0) + if (item->Regen > 0) newbon->HPRegen += item->Regen; - if(item->ManaRegen > 0) + if (item->ManaRegen > 0) newbon->ManaRegen += item->ManaRegen; - if(item->EnduranceRegen > 0) + if (item->EnduranceRegen > 0) newbon->EnduranceRegen += item->EnduranceRegen; - if(item->DamageShield > 0) { - if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) + if (item->DamageShield > 0) { + if ((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) newbon->DamageShield = RuleI(Character, ItemDamageShieldCap); else newbon->DamageShield += item->DamageShield; } - if(item->SpellShield > 0) { - if((newbon->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)) + if (item->SpellShield > 0) { + if ((newbon->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)) newbon->SpellShield = RuleI(Character, ItemSpellShieldingCap); else newbon->SpellShield += item->SpellShield; } - if(item->Shielding > 0) { - if((newbon->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)) + if (item->Shielding > 0) { + if ((newbon->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)) newbon->MeleeMitigation = RuleI(Character, ItemShieldingCap); else newbon->MeleeMitigation += item->Shielding; } - if(item->StunResist > 0) { - if((newbon->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)) + if (item->StunResist > 0) { + if ((newbon->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)) newbon->StunResist = RuleI(Character, ItemStunResistCap); else newbon->StunResist += item->StunResist; } - if(item->StrikeThrough > 0) { - if((newbon->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)) + if (item->StrikeThrough > 0) { + if ((newbon->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)) newbon->StrikeThrough = RuleI(Character, ItemStrikethroughCap); else newbon->StrikeThrough += item->StrikeThrough; } - if(item->Avoidance > 0) { - if((newbon->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)) + if (item->Avoidance > 0) { + if ((newbon->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)) newbon->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap); else newbon->AvoidMeleeChance += item->Avoidance; } - if(item->Accuracy > 0) { - if((newbon->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)) + if (item->Accuracy > 0) { + if ((newbon->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)) newbon->HitChance = RuleI(Character, ItemAccuracyCap); else newbon->HitChance += item->Accuracy; } - if(item->CombatEffects > 0) { - if((newbon->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)) + if (item->CombatEffects > 0) { + if ((newbon->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)) newbon->ProcChance = RuleI(Character, ItemCombatEffectsCap); else newbon->ProcChance += item->CombatEffects; } - if(item->DotShielding > 0) { - if((newbon->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)) + if (item->DotShielding > 0) { + if ((newbon->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)) newbon->DoTShielding = RuleI(Character, ItemDoTShieldingCap); else newbon->DoTShielding += item->DotShielding; } - if(item->HealAmt > 0) { - if((newbon->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)) + if (item->HealAmt > 0) { + if ((newbon->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)) newbon->HealAmt = RuleI(Character, ItemHealAmtCap); else newbon->HealAmt += item->HealAmt; } - if(item->SpellDmg > 0) { - if((newbon->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)) + if (item->SpellDmg > 0) { + if ((newbon->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)) newbon->SpellDmg = RuleI(Character, ItemSpellDmgCap); else newbon->SpellDmg += item->SpellDmg; } - if(item->Clairvoyance > 0) { - if((newbon->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)) + if (item->Clairvoyance > 0) { + if ((newbon->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)) newbon->Clairvoyance = RuleI(Character, ItemClairvoyanceCap); else newbon->Clairvoyance += item->Clairvoyance; } - if(item->DSMitigation > 0) { - if((newbon->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)) + if (item->DSMitigation > 0) { + if ((newbon->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)) newbon->DSMitigation = RuleI(Character, ItemDSMitigationCap); else newbon->DSMitigation += item->DSMitigation; @@ -7908,45 +7286,45 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool { case EQ::item::ItemTypeAllInstrumentTypes: // (e.g. Singing Short Sword) { - if(item->BardValue > newbon->singingMod) + if (item->BardValue > newbon->singingMod) newbon->singingMod = item->BardValue; - if(item->BardValue > newbon->brassMod) + if (item->BardValue > newbon->brassMod) newbon->brassMod = item->BardValue; - if(item->BardValue > newbon->stringedMod) + if (item->BardValue > newbon->stringedMod) newbon->stringedMod = item->BardValue; - if(item->BardValue > newbon->percussionMod) + if (item->BardValue > newbon->percussionMod) newbon->percussionMod = item->BardValue; - if(item->BardValue > newbon->windMod) + if (item->BardValue > newbon->windMod) newbon->windMod = item->BardValue; break; } case EQ::item::ItemTypeSinging: { - if(item->BardValue > newbon->singingMod) + if (item->BardValue > newbon->singingMod) newbon->singingMod = item->BardValue; break; } case EQ::item::ItemTypeWindInstrument: { - if(item->BardValue > newbon->windMod) + if (item->BardValue > newbon->windMod) newbon->windMod = item->BardValue; break; } case EQ::item::ItemTypeStringedInstrument: { - if(item->BardValue > newbon->stringedMod) + if (item->BardValue > newbon->stringedMod) newbon->stringedMod = item->BardValue; break; } case EQ::item::ItemTypeBrassInstrument: { - if(item->BardValue > newbon->brassMod) + if (item->BardValue > newbon->brassMod) newbon->brassMod = item->BardValue; break; } case EQ::item::ItemTypePercussionInstrument: { - if(item->BardValue > newbon->percussionMod) + if (item->BardValue > newbon->percussionMod) newbon->percussionMod = item->BardValue; break; } @@ -7961,7 +7339,7 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool } if (item->ExtraDmgSkill != 0 && item->ExtraDmgSkill <= EQ::skills::HIGHEST_SKILL) { - if((newbon->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)) + if ((newbon->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)) newbon->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap); else newbon->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt; @@ -7977,11 +7355,11 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool int Bot::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) { - if( (reclevel > 0) && (level < reclevel) ) + if ( (reclevel > 0) && (level < reclevel) ) { int32 statmod = (level * 10000 / reclevel) * basestat; - if( statmod < 0 ) + if ( statmod < 0 ) { statmod -= 5000; return (statmod/10000); @@ -7998,21 +7376,21 @@ int Bot::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) // This method is intended to call all necessary methods to do all bot stat calculations, including spell buffs, equipment, AA bonsues, etc. void Bot::CalcBotStats(bool showtext) { - if(!GetBotOwner()) + if (!GetBotOwner()) return; - if(showtext) { + if (showtext) { GetBotOwner()->Message(Chat::Yellow, "Updating %s...", GetCleanName()); } // this code is annoying since many classes change their name and illusions change the race id - /*if(!IsValidRaceClassCombo()) { + /*if (!IsValidRaceClassCombo()) { GetBotOwner()->Message(Chat::Yellow, "A %s - %s bot was detected. Is this Race/Class combination allowed?.", GetRaceIDName(GetRace()), GetClassIDName(GetClass(), GetLevel())); GetBotOwner()->Message(Chat::Yellow, "Previous Bots Code releases did not check Race/Class combinations during create."); GetBotOwner()->Message(Chat::Yellow, "Unless you are experiencing heavy lag, you should delete and remake this bot."); }*/ - if(GetBotOwner()->GetLevel() != GetLevel()) + if (GetBotOwner()->GetLevel() != GetLevel()) SetLevel(GetBotOwner()->GetLevel()); for (int sindex = 0; sindex <= EQ::skills::HIGHEST_SKILL; ++sindex) { @@ -8028,29 +7406,29 @@ void Bot::CalcBotStats(bool showtext) { LoadAAs(); GenerateSpecialAttacks(); - if(showtext) { + if (showtext) { GetBotOwner()->Message(Chat::Yellow, "Base stats:"); GetBotOwner()->Message(Chat::Yellow, "Level: %i HP: %i AC: %i Mana: %i STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetLevel(), base_hp, AC, max_mana, STR, STA, DEX, AGI, INT, WIS, CHA); GetBotOwner()->Message(Chat::Yellow, "Resists-- Magic: %i, Poison: %i, Fire: %i, Cold: %i, Disease: %i, Corruption: %i.",MR,PR,FR,CR,DR,Corrup); // Test Code - if(GetClass() == BARD) + if (GetClass() == BARD) GetBotOwner()->Message(Chat::Yellow, "Bard Skills-- Brass: %i, Percussion: %i, Singing: %i, Stringed: %i, Wind: %i", GetSkill(EQ::skills::SkillBrassInstruments), GetSkill(EQ::skills::SkillPercussionInstruments), GetSkill(EQ::skills::SkillSinging), GetSkill(EQ::skills::SkillStringedInstruments), GetSkill(EQ::skills::SkillWindInstruments)); } - //if(Save()) + //if (Save()) // GetBotOwner()->CastToClient()->Message(Chat::White, "%s saved.", GetCleanName()); //else // GetBotOwner()->CastToClient()->Message(Chat::White, "%s save failed!", GetCleanName()); CalcBonuses(); - if(showtext) { + if (showtext) { GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName()); GetBotOwner()->Message(Chat::Yellow, "Level: %i HP: %i AC: %i Mana: %i STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetLevel(), max_hp, GetAC(), max_mana, GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); GetBotOwner()->Message(Chat::Yellow, "Resists-- Magic: %i, Poison: %i, Fire: %i, Cold: %i, Disease: %i, Corruption: %i.",GetMR(),GetPR(),GetFR(),GetCR(),GetDR(),GetCorrup()); // Test Code - if(GetClass() == BARD) { + if (GetClass() == BARD) { GetBotOwner()->Message(Chat::Yellow, "Bard Skills-- Brass: %i, Percussion: %i, Singing: %i, Stringed: %i, Wind: %i", GetSkill(EQ::skills::SkillBrassInstruments) + GetBrassMod(), GetSkill(EQ::skills::SkillPercussionInstruments) + GetPercMod(), @@ -8074,13 +7452,15 @@ bool Bot::CheckLoreConflict(const EQ::ItemData* item) { } bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { + + if ((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { LogError("[EntityList::Bot_AICheckCloseBeneficialSpells] detrimental spells requested"); return false; } - if(!caster || !caster->AI_HasSpells()) + if (!caster || !caster->AI_HasSpells()) { return false; + } if (iChance < 100) { uint8 tmp = zone->random.Int(1, 100); @@ -8090,33 +7470,72 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl uint8 botCasterClass = caster->GetClass(); - if( iSpellTypes == SpellType_Heal ) { - if( botCasterClass == CLERIC || botCasterClass == DRUID || botCasterClass == SHAMAN) { - if(caster->HasGroup()) { + if (iSpellTypes == SpellType_Heal) { + if ( botCasterClass == CLERIC || botCasterClass == DRUID || botCasterClass == SHAMAN) { + if (caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && !g->members[i]->qglobal) { - if(g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i] && !g->members[i]->qglobal) { + if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < 95) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + } else if ((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < 95) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < 80) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + } else if (g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < 80) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } else if(g->members[i]->GetHPRatio() < 70) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + } else if (g->members[i]->GetHPRatio() < 70) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; } } - if(g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) { - if(g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER ) + if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) { + if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER ) continue; - if(caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) + if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) + return true; + } + } + } + } + else if (caster->IsRaidGrouped()) + { + //added raid check + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 gid = raid->GetGroup(caster->GetName()); + if (gid < MAX_RAID_GROUPS) { + std::vector raid_group_members = raid->GetRaidGroupMembers(gid); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + //for (auto& iter : raid->GetRaidGroupMembers(g)) { + if (iter->member && !iter->member->qglobal) { + if (iter->member->IsClient() && iter->member->GetHPRatio() < 90) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if ((iter->member->GetClass() == WARRIOR || iter->member->GetClass() == PALADIN || iter->member->GetClass() == SHADOWKNIGHT) && iter->member->GetHPRatio() < 95) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if (iter->member->GetClass() == ENCHANTER && iter->member->GetHPRatio() < 80) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if (iter->member->GetHPRatio() < 70) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + + } + + if (iter->member && !iter->member->qglobal && iter->member->HasPet() && iter->member->GetPet()->GetHPRatio() < 50) { + if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && iter->member->IsCasting() && iter->member->GetClass() != ENCHANTER) + continue; + + if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) return true; } } @@ -8124,52 +7543,88 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } - if( botCasterClass == PALADIN || botCasterClass == BEASTLORD || botCasterClass == RANGER) { - if(caster->HasGroup()) { - Group *g = caster->GetGroup(); - float hpRatioToHeal = 25.0f; - switch(caster->GetBotStance()) { - case EQ::constants::stanceReactive: - case EQ::constants::stanceBalanced: - hpRatioToHeal = 50.0f; - break; - case EQ::constants::stanceBurn: - case EQ::constants::stanceBurnAE: - hpRatioToHeal = 20.0f; - break; - case EQ::constants::stanceAggressive: - case EQ::constants::stanceEfficient: - default: - hpRatioToHeal = 25.0f; - break; - } + if ((botCasterClass == PALADIN || botCasterClass == BEASTLORD || botCasterClass == RANGER) && (caster->HasGroup() || caster->IsRaidGrouped())) { + float hpRatioToHeal = 25.0f; + switch(caster->GetBotStance()) { + case EQ::constants::stanceReactive: + case EQ::constants::stanceBalanced: + hpRatioToHeal = 50.0f; + break; + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + hpRatioToHeal = 20.0f; + break; + case EQ::constants::stanceAggressive: + case EQ::constants::stanceEfficient: + default: + hpRatioToHeal = 25.0f; + break; + } + Group* g = caster->GetGroup(); + uint32 gid = RAID_GROUPLESS; + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + if (raid) { + gid = raid->GetGroup(caster->GetName()); + } - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && !g->members[i]->qglobal) { - if(g->members[i]->IsClient() && g->members[i]->GetHPRatio() < hpRatioToHeal) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < hpRatioToHeal) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < hpRatioToHeal) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } else if(g->members[i]->GetHPRatio() < hpRatioToHeal/2) { - if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) - return true; - } - } - - if(g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 25) { - if(g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER ) - continue; - - if(caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i] && !g->members[i]->qglobal) { + if (g->members[i]->IsClient() && g->members[i]->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + return true; + } else if ((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + return true; + } else if (g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) + return true; + } else if (g->members[i]->GetHPRatio() < hpRatioToHeal/2) { + if (caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; } } + + if (g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 25) { + if (g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER ) + continue; + + if (caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Heal)) + return true; + } + } + } + else if (gid < MAX_RAID_GROUPS) + { + std::vector raid_group_members = raid->GetRaidGroupMembers(gid); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + //for (auto& iter : raid->GetRaidGroupMembers(gid)) { + if (iter->member && !iter->member->qglobal) { + if (iter->member->IsClient() && iter->member->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if ((iter->member->GetClass() == WARRIOR || iter->member->GetClass() == PALADIN || iter->member->GetClass() == SHADOWKNIGHT) && iter->member->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if (iter->member->GetClass() == ENCHANTER && iter->member->GetHPRatio() < hpRatioToHeal) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + else if (iter->member->GetHPRatio() < hpRatioToHeal / 2) { + if (caster->AICastSpell(iter->member, 100, SpellType_Heal)) + return true; + } + } + + if (iter->member && !iter->member->qglobal && iter->member->HasPet() && iter->member->GetPet()->GetHPRatio() < 25) { + if (iter->member->GetPet()->GetOwner() != caster && caster->IsEngaged() && iter->member->IsCasting() && iter->member->GetClass() != ENCHANTER) + continue; + + if (caster->AICastSpell(iter->member->GetPet(), 100, SpellType_Heal)) + return true; + } } } } @@ -8178,18 +7633,31 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl if (iSpellTypes == SpellType_Buff) { uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(SpellType_Buff) : 100; if (botCasterClass == BARD) { - if(caster->AICastSpell(caster, chanceToCast, SpellType_Buff)) + if (caster->AICastSpell(caster, chanceToCast, SpellType_Buff)) { return true; - else + } else return false; } + if (caster->IsRaidGrouped()) { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 g = raid->GetGroup(caster->GetName()); + if (g < MAX_RAID_GROUPS) { + std::vector raid_group_members = raid->GetRaidGroupMembers(g); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member) { + if (caster->AICastSpell(iter->member, chanceToCast, SpellType_Buff) || caster->AICastSpell(iter->member->GetPet(), chanceToCast, SpellType_Buff)) + return true; + } + } + } + } if (caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i]) { - if(caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff) || caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i]) { + if (caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff) || caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) return true; } } @@ -8197,20 +7665,41 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } - if( iSpellTypes == SpellType_Cure) { - if(caster->HasGroup()) { + if ( iSpellTypes == SpellType_Cure) { + if (caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && caster->GetNeedsCured(g->members[i])) { - if(caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) + if (g) { + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (g->members[i] && caster->GetNeedsCured(g->members[i])) { + if (caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) return true; - else if(botCasterClass == BARD) + else if (botCasterClass == BARD) return false; } - if(g->members[i] && g->members[i]->GetPet() && caster->GetNeedsCured(g->members[i]->GetPet())) { - if(caster->AICastSpell(g->members[i]->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure)/4, SpellType_Cure)) + if (g->members[i] && g->members[i]->GetPet() && caster->GetNeedsCured(g->members[i]->GetPet())) { + if (caster->AICastSpell(g->members[i]->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure)/4, SpellType_Cure)) + return true; + } + } + } + } + else if (caster->IsRaidGrouped()) + { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 gid = raid->GetGroup(caster->GetName()); + if (gid < MAX_RAID_GROUPS) { + std::vector raid_group_members = raid->GetRaidGroupMembers(gid); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member && caster->GetNeedsCured(iter->member)) { + if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) + return true; + else if (botCasterClass == BARD) + return false; + } + + if (iter->member && iter->member->GetPet() && caster->GetNeedsCured(iter->member->GetPet())) { + if (caster->AICastSpell(iter->member->GetPet(), (int)caster->GetChanceToCastBySpellType(SpellType_Cure) / 4, SpellType_Cure)) return true; } } @@ -8233,14 +7722,44 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } + else if (caster->IsRaidGrouped()) + { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 gid = raid->GetGroup(caster->GetName()); + if (gid < MAX_RAID_GROUPS) { + std::vector raid_group_members = raid->GetRaidGroupMembers(gid); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member && caster->GetNeedsHateRedux(iter->member)) { + if (caster->AICastSpell(iter->member, caster->GetChanceToCastBySpellType(SpellType_HateRedux), SpellType_HateRedux)) + return true; + } + } + } + } + } if (iSpellTypes == SpellType_PreCombatBuff) { if (botCasterClass == BARD || caster->IsEngaged()) return false; - if (caster->HasGroup()) { - Group *g = caster->GetGroup(); + //added raid check + if (caster->IsRaidGrouped()) { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 g = raid->GetGroup(caster->GetName()); + if (g < MAX_RAID_GROUPS) { + std::vector raid_group_members = raid->GetRaidGroupMembers(g); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member && + (caster->AICastSpell(iter->member, iChance, SpellType_PreCombatBuff) || + caster->AICastSpell(iter->member->GetPet(), iChance, SpellType_PreCombatBuff)) + ) { + return true; + } + } + } + } else if (caster->HasGroup()) { + const auto g = caster->GetGroup(); if (g) { for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { if (g->members[i]) { @@ -8282,14 +7801,13 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl Mob* EntityList::GetMobByBotID(uint32 botID) { Mob* Result = nullptr; - if(botID > 0) { - auto it = mob_list.begin(); - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - if(!it->second) + if (botID > 0) { + for (const auto& m: mob_list) { + if (!m.second) continue; - if(it->second->IsBot() && it->second->CastToBot()->GetBotID() == botID) { - Result = it->second; + if (m.second->IsBot() && m.second->CastToBot()->GetBotID() == botID) { + Result = m.second; break; } } @@ -8299,10 +7817,10 @@ Mob* EntityList::GetMobByBotID(uint32 botID) { Bot* EntityList::GetBotByBotID(uint32 botID) { Bot* Result = nullptr; - if(botID > 0) { - for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { + if (botID > 0) { + for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotID() == botID) { + if (tempBot && tempBot->GetBotID() == botID) { Result = tempBot; break; } @@ -8311,12 +7829,12 @@ Bot* EntityList::GetBotByBotID(uint32 botID) { return Result; } -Bot* EntityList::GetBotByBotName(std::string botName) { +Bot* EntityList::GetBotByBotName(std::string_view botName) { Bot* Result = nullptr; - if(!botName.empty()) { - for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { + if (!botName.empty()) { + for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && std::string(tempBot->GetName()) == botName) { + if (tempBot && std::string(tempBot->GetName()) == botName) { Result = tempBot; break; } @@ -8388,10 +7906,10 @@ void EntityList::AddBot(Bot *new_bot, bool send_spawn_packet, bool dont_queue) { std::list EntityList::GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID) { std::list Result; - if(botOwnerCharacterID > 0) { - for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { + if (botOwnerCharacterID > 0) { + for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == botOwnerCharacterID) + if (tempBot && tempBot->GetBotOwnerCharacterID() == botOwnerCharacterID) Result.push_back(tempBot); } } @@ -8400,10 +7918,10 @@ std::list EntityList::GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacte bool EntityList::RemoveBot(uint16 entityID) { bool Result = false; - if(entityID > 0) { - for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { + if (entityID > 0) { + for (std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetID() == entityID) { + if (tempBot && tempBot->GetID() == entityID) { bot_list.erase(botListItr); Result = true; break; @@ -8420,11 +7938,11 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { int CurrentCon = 0; Mob* curMob = nullptr; uint32 array_counter = 0; - auto it = mob_list.begin(); - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - curMob = it->second; + + for (const auto& m : mob_list) { + curMob = m.second; if (curMob && DistanceNoZ(curMob->GetPosition(), client->GetPosition()) <= Distance) { - if(curMob->IsTrackable()) { + if (curMob->IsTrackable()) { Mob* cur_entity = curMob; int Extras = (cur_entity->IsBot() || cur_entity->IsPet() || cur_entity->IsFamiliar() || cur_entity->IsClient()); const char *const MyArray[] = { @@ -8462,7 +7980,7 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { for ( MyArraySize = 0; true; MyArraySize++) { if (!(*(MyArray[MyArraySize]))) break; - }; + } if (NamedOnly) { bool ContinueFlag = false; const char *CurEntityName = cur_entity->GetName(); @@ -8470,15 +7988,15 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { if (!strncasecmp(CurEntityName, MyArray[Index], strlen(MyArray[Index])) || (Extras)) { ContinueFlag = true; break; - }; - }; + } + } if (ContinueFlag) continue; - }; + } CurrentCon = client->GetLevelCon(cur_entity->GetLevel()); - if(CurrentCon != LastCon) { - if(LastCon != -1) + if (CurrentCon != LastCon) { + if (LastCon != -1) WindowText += ""; LastCon = CurrentCon; @@ -8511,7 +8029,7 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { } WindowText += cur_entity->GetCleanName(); WindowText += "
"; - if(strlen(WindowText.c_str()) > 4000) { + if (strlen(WindowText.c_str()) > 4000) { WindowText += "

List truncated... too many mobs to display"; break; } @@ -8556,38 +8074,41 @@ void EntityList::ScanCloseClientMobs(std::unordered_map& close_mob LogAIScanCloseDetail("Close Client Mob List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName()); } -uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets) { - uint8 needHealed = 0; - Group *g = nullptr; - if(HasGroup()) { - g = GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && !g->members[i]->qglobal) { - if(g->members[i]->GetHPRatio() <= hpr) - needHealed++; +uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid) { - if(includePets) { - if(g->members[i]->GetPet() && g->members[i]->GetPet()->GetHPRatio() <= hpr) - needHealed++; + uint8 need_healed = 0; + if (HasGroup()) { + + auto group_members = GetGroup(); + if (group_members) { + + for (auto member : group_members->members) { + if (member && !member->qglobal) { + + if (member->GetHPRatio() <= hpr) { + need_healed++; + } + + if (includePets && member->GetPet() && member->GetPet()->GetHPRatio() <= hpr) { + need_healed++; } } } } } - return needHealed; + return GetNumberNeedingHealedInRaidGroup(need_healed, hpr, includePets, raid); } int Bot::GetRawACNoShield(int &shield_ac) { int ac = itembonuses.AC + spellbonuses.AC; shield_ac = 0; EQ::ItemInstance* inst = GetBotItem(EQ::invslot::slotSecondary); - if(inst) { + if (inst) { if (inst->GetItem()->ItemType == EQ::item::ItemTypeShield) { ac -= inst->GetItem()->AC; shield_ac = inst->GetItem()->AC; for (uint8 i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if(inst->GetAugment(i)) { + if (inst->GetAugment(i)) { ac -= inst->GetAugment(i)->GetItem()->AC; shield_ac += inst->GetAugment(i)->GetItem()->AC; } @@ -8603,7 +8124,7 @@ uint32 Bot::CalcCurrentWeight() { uint32 Total = 0; for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; ++i) { inst = GetBotItem(i); - if(inst) { + if (inst) { TempItem = inst->GetItem(); if (TempItem) Total += TempItem->Weight; @@ -8620,7 +8141,7 @@ uint32 Bot::CalcCurrentWeight() { int Bot::GroupLeadershipAAHealthEnhancement() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) + if (!g || (g->GroupCount() < 3)) return 0; switch(g->GetLeadershipAA(groupAAHealthEnhancement)) { @@ -8638,7 +8159,7 @@ int Bot::GroupLeadershipAAHealthEnhancement() { int Bot::GroupLeadershipAAManaEnhancement() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) + if (!g || (g->GroupCount() < 3)) return 0; switch(g->GetLeadershipAA(groupAAManaEnhancement)) { @@ -8656,7 +8177,7 @@ int Bot::GroupLeadershipAAManaEnhancement() { int Bot::GroupLeadershipAAHealthRegeneration() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) + if (!g || (g->GroupCount() < 3)) return 0; switch(g->GetLeadershipAA(groupAAHealthRegeneration)) { @@ -8676,7 +8197,7 @@ int Bot::GroupLeadershipAAHealthRegeneration() { int Bot::GroupLeadershipAAOffenseEnhancement() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) + if (!g || (g->GroupCount() < 3)) return 0; switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) { @@ -8698,16 +8219,16 @@ int Bot::GroupLeadershipAAOffenseEnhancement() { bool Bot::GetNeedsCured(Mob *tar) { bool needCured = false; - if(tar) { - if(tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { + if (tar) { + if (tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { uint32 buff_count = tar->GetMaxTotalSlots(); int buffsWithCounters = 0; needCured = true; for (unsigned int j = 0; j < buff_count; j++) { - if(IsValidSpell(tar->GetBuffs()[j].spellid)) { - if(CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { + if (IsValidSpell(tar->GetBuffs()[j].spellid)) { + if (CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { buffsWithCounters++; - if(buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { + if (buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { needCured = false; break; } @@ -8758,15 +8279,15 @@ bool Bot::GetNeedsHateRedux(Mob *tar) { bool Bot::HasOrMayGetAggro() { bool mayGetAggro = false; - if(GetTarget() && GetTarget()->GetHateTop()) { + if (GetTarget() && GetTarget()->GetHateTop()) { Mob *topHate = GetTarget()->GetHateTop(); - if(topHate == this) + if (topHate == this) mayGetAggro = true; else { uint32 myHateAmt = GetTarget()->GetHateAmount(this); uint32 topHateAmt = GetTarget()->GetHateAmount(topHate); - if(myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt / topHateAmt) * 100) > 90) + if (myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt / topHateAmt) * 100) > 90) mayGetAggro = true; } } @@ -8787,34 +8308,34 @@ void Bot::BotGroupSay(Mob *speaker, const char *msg, ...) { va_start(ap, msg); vsnprintf(buf, 1000, msg, ap); va_end(ap); - if(speaker->HasGroup()) { + if (speaker->HasGroup()) { Group *g = speaker->GetGroup(); - if(g) + if (g) g->GroupMessage(speaker->CastToMob(), 0, 100, buf); } else speaker->Say("%s", buf); } bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { - if(!IsValidSpell(spell_id)) { + if (!IsValidSpell(spell_id)) { BotGroupSay(this, "Not a valid spell."); return false; } const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[GetClass() - 1]; - if(level_to_use == 255 || level_to_use > GetLevel()) { + if (level_to_use == 255 || level_to_use > GetLevel()) { return false; } - if(GetEndurance() > spell.endurance_cost) + if (GetEndurance() > spell.endurance_cost) SetEndurance(GetEndurance() - spell.endurance_cost); else return false; - if(spell.recast_time > 0) { - if(CheckDisciplineRecastTimers(this, spells[spell_id].timer_id)) { - if(spells[spell_id].timer_id > 0 && spells[spell_id].timer_id < MAX_DISCIPLINE_TIMERS) + if (spell.recast_time > 0) { + if (CheckDisciplineRecastTimers(this, spells[spell_id].timer_id)) { + if (spells[spell_id].timer_id > 0 && spells[spell_id].timer_id < MAX_DISCIPLINE_TIMERS) SetDisciplineRecastTimer(spells[spell_id].timer_id, spell.recast_time); } else { uint32 remaining_time = (GetDisciplineRemainingTime(this, spells[spell_id].timer_id) / 1000); @@ -8830,7 +8351,7 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { } } - if(IsCasting()) + if (IsCasting()) InterruptSpell(); CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); @@ -9057,23 +8578,7 @@ std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) return saylink; } -void Bot::StopMoving() -{ - //SetCombatJitterFlag(false); - //m_combat_jitter_timer.Start(zone->random.Int(BOT_COMBAT_JITTER_INTERVAL_MIN, BOT_COMBAT_JITTER_INTERVAL_MAX)); - - Mob::StopMoving(); -} - -void Bot::StopMoving(float new_heading) -{ - //SetCombatJitterFlag(false); - //m_combat_jitter_timer.Start(zone->random.Int(BOT_COMBAT_JITTER_INTERVAL_MIN, BOT_COMBAT_JITTER_INTERVAL_MAX)); - - Mob::StopMoving(new_heading); -} - -void Bot::SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leader_id) +void Bot::SpawnBotGroupByName(Client* c, const std::string& botgroup_name, uint32 leader_id) { auto leader = Bot::LoadBot(leader_id); if (!leader) { @@ -9236,7 +8741,7 @@ void Bot::SendPayload(int payload_id, std::string payload_value) } } -void Bot::OwnerMessage(std::string message) +void Bot::OwnerMessage(const std::string& message) { if (!GetBotOwner() || !GetBotOwner()->IsClient()) { return; @@ -9307,7 +8812,7 @@ bool Bot::GetBotDataBuckets() return true; } -bool Bot::CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison) +bool Bot::CheckDataBucket(const std::string& bucket_name, const std::string& bucket_value, uint8 bucket_comparison) { if (!bucket_name.empty() && !bucket_value.empty()) { auto full_name = fmt::format( @@ -9661,7 +9166,7 @@ std::vector Bot::GetApplySpellList( if (apply_type == ApplySpellType::Raid && IsRaidGrouped()) { auto* r = GetRaid(); - auto group_id = r->GetGroup(this->GetCleanName()); + auto group_id = r->GetGroup(GetCleanName()); if (r && EQ::ValueWithin(group_id, 0, (MAX_RAID_GROUPS - 1))) { for (auto i = 0; i < MAX_RAID_MEMBERS; i++) { auto* m = r->members[i].member; @@ -9835,4 +9340,40 @@ float Bot::GetBotCasterMaxRange(float melee_distance_max) {// Calculate caster d return caster_distance_max; } +bool Bot::CheckSpawnConditions(Client* c) { + + if (c->GetFeigned()) { + c->Message(Chat::White, "You cannot spawn a bot-group while feigned."); + return false; + } + + auto* owner_group = c->GetGroup(); + if (owner_group) { + std::list member_list; + owner_group->GetClientList(member_list); + member_list.remove(nullptr); + + for (auto member_iter : member_list) { + if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); + return false; + } + } + } else { + if (c->GetAggroCount() > 0) { + c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); + return false; + } + } + + Raid* raid = entity_list.GetRaidByClient(c); + if (raid && raid->IsEngaged()) { + c->Message(Chat::White, "You cannot spawn bots while your raid is engaged."); + return false; + } + + return true; +} + + uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; diff --git a/zone/bot.h b/zone/bot.h index a648868bd..f383304ce 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -33,6 +33,7 @@ #include "../common/global_define.h" #include "guild_mgr.h" #include "worldserver.h" +#include "raids.h" #include @@ -42,9 +43,6 @@ constexpr uint32 BOT_FOLLOW_DISTANCE_WALK = 1000; // as DSq value (~31.623 units constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds -//constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 5000; // 5 seconds -//constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 20000; // 20 seconds - extern WorldServer worldserver; constexpr int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this @@ -53,8 +51,6 @@ constexpr int MaxDisciplineTimer = 10; constexpr int DisciplineReuseStart = MaxSpellTimer + 1; constexpr int MaxTimer = MaxSpellTimer + MaxDisciplineTimer; - - // nHSND negative Healer/Slower/Nuker/Doter // pH positive Healer // pS positive Slower @@ -141,13 +137,13 @@ public: void Damage(Mob* from, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) override; - bool HasRaid() final { return (GetRaid() ? true : false); } - bool HasGroup() final { return (GetGroup() ? true : false); } - Raid* GetRaid() final { return entity_list.GetRaidByMob(this); } + bool HasRaid() final { return GetRaid() != nullptr; } + bool HasGroup() final { return GetGroup() != nullptr; } + Raid* GetRaid() final { return entity_list.GetRaidByBot(this); } Group* GetGroup() final { return entity_list.GetGroupByMob(this); } // Common, but informal "interfaces" with Client object - uint32 CharacterID() { return GetBotID(); } // Just returns the Bot Id + uint32 CharacterID() const { return GetBotID(); } inline bool IsInAGuild() const { return (_guildId != GUILD_NONE && _guildId != 0); } inline bool IsInGuild(uint32 in_gid) const { return (in_gid == _guildId && IsInAGuild()); } inline uint32 GuildID() const { return _guildId; } @@ -173,20 +169,19 @@ public: int GetHandToHandDamage(void) override; bool TryFinishingBlow(Mob *defender, int64 &damage) override; void DoRiposte(Mob* defender) override; - inline int32 GetATK() const override { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQ::skills::SkillOffense)) * 9 / 10); } + inline int32 GetATK() { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQ::skills::SkillOffense)) * 9 / 10); } inline int32 GetATKBonus() const override { return itembonuses.ATK + spellbonuses.ATK; } uint32 GetTotalATK(); uint32 GetATKRating(); uint16 GetPrimarySkillValue(); uint16 MaxSkill(EQ::skills::SkillType skillid, uint16 class_, uint16 level) const; - inline uint16 MaxSkill(EQ::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } + inline uint16 MaxSkill(EQ::skills::SkillType skillid) { return MaxSkill(skillid, GetClass(), GetLevel()); } int GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target = nullptr) override; void DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false); void TryBackstab(Mob *other,int ReuseTime = 10) override; void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10) override; void RogueAssassinate(Mob* other) override; void DoClassAttacks(Mob *target, bool IsRiposte=false); - bool CanDoSpecialAttack(Mob *other); void CalcBonuses() override; void CalcItemBonuses(StatBonuses* newbon); void AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool isAug = false, bool isTribute = false, int rec_override = 0); @@ -197,7 +192,7 @@ public: bool IsNPC() const override { return false; } Mob* GetOwner() override; Mob* GetOwnerOrSelf() override; - inline bool HasOwner() override { return (GetBotOwner() ? true : false); } + inline bool HasOwner() override { return GetBotOwner() != nullptr; } int64 CalcMaxMana() override; void SetAttackTimer() override; uint64 GetClassHPFactor(); @@ -215,36 +210,35 @@ public: void Stand(); bool IsSitting() const override; bool IsStanding(); - int GetWalkspeed() const override { return (int)((float)_GetWalkSpeed() * 1.785714285f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143 - int GetRunspeed() const override { return (int)((float)_GetRunSpeed() * 1.785714285f); } + int GetWalkspeed() { return (int)((float)_GetWalkSpeed() * 1.785714285f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143 + int GetRunspeed() { return (int)((float)_GetRunSpeed() * 1.785714285f); } void WalkTo(float x, float y, float z) override; void RunTo(float x, float y, float z) override; - void StopMoving() override; - void StopMoving(float new_heading) override; - //bool GetCombatJitterFlag() { return m_combat_jitter_flag; } - bool GetGuardFlag() { return m_guard_flag; } + + bool GetGuardFlag() const { return m_guard_flag; } void SetGuardFlag(bool flag = true) { m_guard_flag = flag; } - bool GetHoldFlag() { return m_hold_flag; } + bool GetHoldFlag() const { return m_hold_flag; } void SetHoldFlag(bool flag = true) { m_hold_flag = flag; } - bool GetAttackFlag() { return m_attack_flag; } + bool GetAttackFlag() const { return m_attack_flag; } void SetAttackFlag(bool flag = true) { m_attack_flag = flag; } - bool GetAttackingFlag() { return m_attacking_flag; } - bool GetPullFlag() { return m_pull_flag; } + bool GetAttackingFlag() const { return m_attacking_flag; } + bool GetPullFlag() const { return m_pull_flag; } void SetPullFlag(bool flag = true) { m_pull_flag = flag; } - bool GetPullingFlag() { return m_pulling_flag; } - bool GetReturningFlag() { return m_returning_flag; } + bool GetPullingFlag() const { return m_pulling_flag; } + bool GetReturningFlag() const { return m_returning_flag; } bool UseDiscipline(uint32 spell_id, uint32 target); - uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets); + uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid); + uint8 GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid); bool GetNeedsCured(Mob *tar); bool GetNeedsHateRedux(Mob *tar); bool HasOrMayGetAggro(); void SetDefaultBotStance(); - void SetSurname(std::string bot_surname); - void SetTitle(std::string bot_title); - void SetSuffix(std::string bot_suffix); - std::string GetSurname() { return _surname; } - std::string GetTitle() { return _title; } - std::string GetSuffix() { return _suffix; } + void SetSurname(std::string_view bot_surname); + void SetTitle(std::string_view bot_title); + void SetSuffix(std::string_view bot_suffix); + std::string GetSurname() const { return _surname; } + std::string GetTitle() const { return _title; } + std::string GetSuffix() const { return _suffix; } inline virtual int32 GetMaxStat(); inline virtual int32 GetMaxResist(); inline virtual int32 GetMaxSTR(); @@ -282,19 +276,18 @@ public: uint32 CalcCurrentWeight(); int GroupLeadershipAAHealthEnhancement(); int GroupLeadershipAAManaEnhancement(); - int GroupLeadershipAAHealthRegeneration(); + int GroupLeadershipAAHealthRegeneration(); int GroupLeadershipAAOffenseEnhancement(); void CalcRestState(); - int64 CalcMaxEndurance(); //This calculates the maximum endurance we can have - int64 CalcBaseEndurance(); //Calculates Base End - int64 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen() - int64 GetEndurance() const {return cur_end;} //This gets our current endurance - int64 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call - int64 CalcEnduranceRegenCap(); - inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); } - void SetEndurance(int32 newEnd); //This sets the current endurance to the new value - void DoEnduranceRegen(); //This Regenerates endurance - void DoEnduranceUpkeep(); //does the endurance upkeep + int64 CalcMaxEndurance(); + int64 CalcBaseEndurance(); + int64 CalcEnduranceRegen(); + int64 GetEndurance() const override {return cur_end;} + int64 GetMaxEndurance() const override {return max_end;} + int64 CalcEnduranceRegenCap(); + inline uint8 GetEndurancePercent() override { return (uint8)((float)cur_end / (float)max_end * 100.0f); } + void SetEndurance(int32 newEnd) override; + void DoEnduranceUpkeep(); bool AI_AddBotSpells(uint32 bot_spell_id); void AddSpellToBotList( @@ -330,19 +323,20 @@ public: ); void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); + // AI Methods bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes); bool AI_EngagedCastCheck() override; bool AI_PursueCastCheck() override; bool AI_IdleCastCheck() override; bool AIHealRotation(Mob* tar, bool useFastHeals); - bool GetPauseAI() { return _pauseAI; } + bool GetPauseAI() const { return _pauseAI; } void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } - uint8 GetStopMeleeLevel() { return _stopMeleeLevel; } + uint8 GetStopMeleeLevel() const { return _stopMeleeLevel; } void SetStopMeleeLevel(uint8 level); void SetGuardMode(); void SetHoldMode(); - uint32 GetBotCasterRange() { return m_bot_caster_range; } + uint32 GetBotCasterRange() const { return m_bot_caster_range; } bool IsValidSpellRange(uint16 spell_id, Mob const* tar); // Bot AI Methods @@ -375,7 +369,7 @@ public: bool IsImmuneToSpell(uint16 spell_id, Mob *caster) override; 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); + uint32* oSpellWillFinish = nullptr, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); inline int64 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr, bool from_buff_tic = false) override { return Mob::GetFocusEffect(type, spell_id, caster, from_buff_tic); } inline bool Attack(Mob* other, int Hand = EQ::invslot::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, @@ -389,40 +383,51 @@ public: bool GetBotOwnerDataBuckets(); bool GetBotDataBuckets(); - bool CheckDataBucket(std::string bucket_name, std::string bucket_value, uint8 bucket_comparison); + bool CheckDataBucket(const std::string& bucket_name, const std::string& bucket_value, uint8 bucket_comparison); // Bot Equipment & Inventory Class Methods void BotTradeAddItem(const EQ::ItemInstance* inst, uint16 slot_id, std::string* error_message, bool save_to_database = true); void EquipBot(std::string* error_message); bool CheckLoreConflict(const EQ::ItemData* item); - void UpdateEquipmentLight() override { m_Light.Type[EQ::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]); } - inline EQ::InventoryProfile& GetInv() { return m_inv; } + void UpdateEquipmentLight() override + { + m_Light.Type[EQ::lightsource::LightEquipment] = m_inv.FindBrightestLightType(); + m_Light.Level[EQ::lightsource::LightEquipment] = EQ::lightsource::TypeToLevel(m_Light.Type[EQ::lightsource::LightEquipment]); + } + + inline EQ::InventoryProfile& GetInv() override { return m_inv; } // Static Class Methods - //static void DestroyBotRaidObjects(Client* client); // Can be removed after bot raids are dumped static Bot* LoadBot(uint32 botID); static uint32 SpawnedBotCount(const uint32 owner_id, uint8 class_id = NO_CLASS); static void LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp); - //static bool SetBotOwnerCharacterID(uint32 botID, uint32 botOwnerCharacterID, std::string* error_message); + static bool IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined); - static Bot* GetBotByBotClientOwnerAndBotName(Client* c, std::string botName); - static void ProcessBotGroupInvite(Client* c, std::string botName); - static void ProcessBotGroupDisband(Client* c, std::string botName); + static Bot* GetBotByBotClientOwnerAndBotName(Client* c, const std::string& botName); + static void ProcessBotGroupInvite(Client* c, std::string const& botName); + static void ProcessBotGroupDisband(Client* c, const std::string& botName); static void BotOrderCampAll(Client* c, uint8 class_id = NO_CLASS); static void ProcessBotInspectionRequest(Bot* inspectedBot, Client* client); static void LoadAndSpawnAllZonedBots(Client* bot_owner); static bool GroupHasBot(Group* group); static Bot* GetFirstBotInGroup(Group* group); static void ProcessClientZoneChange(Client* botOwner); - static void ProcessBotOwnerRefDelete(Mob* botOwner); // Removes a Client* reference when the Client object is destroyed - static void ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild); // Processes a client's request to guild a bot - static bool ProcessGuildRemoval(Client* guildOfficer, std::string botName); // Processes a client's request to deguild a bot + static void ProcessBotOwnerRefDelete(Mob* botOwner); // Removes a Client* reference when the Client object is destroyed static int32 GetSpellRecastTimer(Bot *caster, int timer_index); static bool CheckSpellRecastTimers(Bot *caster, int SpellIndex); static int32 GetDisciplineRecastTimer(Bot *caster, int timer_index); static bool CheckDisciplineRecastTimers(Bot *caster, int timer_index); static uint32 GetDisciplineRemainingTime(Bot *caster, int timer_index); + //Raid methods + static void ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite = false); + static void RemoveBotFromRaid(Bot* bot); + inline void SetDirtyAutoHaters() { m_dirtyautohaters = true; } + static void CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* raid); + static void + ProcessBotGroupAdd(Group* group, Raid* raid, Client* client = nullptr, bool new_raid = false, bool initial = false); + + static std::list GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect); static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType); static std::list GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType); @@ -452,8 +457,8 @@ public: static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target); static NPCType *CreateDefaultNPCTypeStructForBot( - std::string botName, - std::string botLastName, + const std::string& botName, + const std::string& botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, @@ -467,8 +472,8 @@ public: // "GET" Class Methods uint32 GetBotID() const { return _botID; } - uint32 GetBotOwnerCharacterID() { return _botOwnerCharacterID; } - uint32 GetBotSpellID() { return npc_spells_id; } + uint32 GetBotOwnerCharacterID() const { return _botOwnerCharacterID; } + uint32 GetBotSpellID() const { return npc_spells_id; } Mob* GetBotOwner() { return this->_botOwner; } uint32 GetBotArcheryRange(); EQ::ItemInstance* GetBotItem(uint16 slot_id); @@ -486,18 +491,12 @@ public: uint8 GetChanceToCastBySpellType(uint32 spellType); bool GetBotEnforceSpellSetting() { return m_enforce_spell_settings; } float GetBotCasterMaxRange(float melee_distance_max); - bool IsGroupHealer() { return m_CastingRoles.GroupHealer; } - bool IsGroupSlower() { return m_CastingRoles.GroupSlower; } - bool IsGroupNuker() { return m_CastingRoles.GroupNuker; } - bool IsGroupDoter() { return m_CastingRoles.GroupDoter; } + bool IsGroupHealer() const { return m_CastingRoles.GroupHealer; } + bool IsGroupSlower() const { return m_CastingRoles.GroupSlower; } + bool IsGroupNuker() const { return m_CastingRoles.GroupNuker; } + bool IsGroupDoter() const { return m_CastingRoles.GroupDoter; } static void UpdateGroupCastingRoles(const Group* group, bool disband = false); - //bool IsRaidHealer() { return m_CastingRoles.RaidHealer; } - //bool IsRaidSlower() { return m_CastingRoles.RaidSlower; } - //bool IsRaidNuker() { return m_CastingRoles.RaidNuker; } - //bool IsRaidDoter() { return m_CastingRoles.RaidDoter; } - //static void UpdateRaidCastingRoles(const Raid* raid, bool disband = false); - bool IsBotCaster() { return IsCasterClass(GetClass()); } bool IsBotHybrid() { return IsHybridClass(GetClass()); } bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); } @@ -506,7 +505,6 @@ public: bool IsBotFighter() { return IsFighterClass(GetClass()); } bool IsBotNonSpellFighter() { return IsNonSpellFighterClass(GetClass()); } uint8 GetBotClass() { return GetClass(); } - bool CanHeal(); int GetRawACNoShield(int &shield_ac); // new heal rotation code @@ -532,8 +530,8 @@ public: std::shared_ptr* MemberOfHealRotation() { return &m_member_of_heal_rotation; } - bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;} - bool GetShowHelm() { return _showhelm; } + bool GetAltOutOfCombatBehavior() const { return _altoutofcombatbehavior;} + bool GetShowHelm() const { return _showhelm; } inline int32 GetSTR() const override { return STR; } inline int32 GetSTA() const override { return STA; } inline int32 GetDEX() const override { return DEX; } @@ -609,7 +607,6 @@ public: void SetBotCharmer(bool c) { _botCharmer = c; } void SetPetChooser(bool p) { _petChooser = p; } void SetBotOwner(Mob* botOwner) { this->_botOwner = botOwner; } - // void SetBotOwnerCharacterID(uint32 botOwnerCharacterID) { _botOwnerCharacterID = botOwnerCharacterID; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == RANGER ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetBotStance(EQ::constants::StanceType botStance) { if (botStance >= EQ::constants::stancePassive && botStance <= EQ::constants::stanceBurnAE) @@ -650,7 +647,7 @@ public: void SetBotEnforceSpellSetting(bool enforcespellsettings, bool save = false); bool GetBotEnforceSpellSetting() const { return m_enforce_spell_settings; } - static void SpawnBotGroupByName(Client* c, std::string botgroup_name, uint32 leader_id); + static void SpawnBotGroupByName(Client* c, const std::string& botgroup_name, uint32 leader_id); std::string CreateSayLink(Client* botOwner, const char* message, const char* name); @@ -663,8 +660,8 @@ public: // Publicized private functions static NPCType *FillNPCTypeStruct( uint32 botSpellsID, - std::string botName, - std::string botLastName, + const std::string& botName, + const std::string& botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, @@ -722,43 +719,116 @@ public: // New accessors for BotDatabase access bool DeleteBot(); uint32* GetTimers() { return timers; } - uint32 GetLastZoneID() { return _lastZoneId; } - int32 GetBaseAC() { return _baseAC; } - int32 GetBaseATK() { return _baseATK; } - int32 GetBaseSTR() { return _baseSTR; } - int32 GetBaseSTA() { return _baseSTA; } - int32 GetBaseCHA() { return _baseCHA; } - int32 GetBaseDEX() { return _baseDEX; } - int32 GetBaseINT() { return _baseINT; } - int32 GetBaseAGI() { return _baseAGI; } - int32 GetBaseWIS() { return _baseWIS; } - int32 GetBaseFR() { return _baseFR; } - int32 GetBaseCR() { return _baseCR; } - int32 GetBaseMR() { return _baseMR; } - int32 GetBasePR() { return _basePR; } - int32 GetBaseDR() { return _baseDR; } - int32 GetBaseCorrup() { return _baseCorrup; } + uint32 GetLastZoneID() const { return _lastZoneId; } + int32 GetBaseAC() const { return _baseAC; } + int32 GetBaseATK() const { return _baseATK; } + int32 GetBaseSTR() const { return _baseSTR; } + int32 GetBaseSTA() const { return _baseSTA; } + int32 GetBaseCHA() const { return _baseCHA; } + int32 GetBaseDEX() const { return _baseDEX; } + int32 GetBaseINT() const { return _baseINT; } + int32 GetBaseAGI() const { return _baseAGI; } + int32 GetBaseWIS() const { return _baseWIS; } + int32 GetBaseFR() const { return _baseFR; } + int32 GetBaseCR() const { return _baseCR; } + int32 GetBaseMR() const { return _baseMR; } + int32 GetBasePR() const { return _basePR; } + int32 GetBaseDR() const { return _baseDR; } + int32 GetBaseCorrup() const { return _baseCorrup; } void Signal(int signal_id); void SendPayload(int payload_id, std::string payload_value = std::string()); - void OwnerMessage(std::string message); + void OwnerMessage(const std::string& message); + + //Raid additions + Raid* p_raid_instance; + + static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND]; + + bool BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid); + bool BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid); + bool BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los); + bool BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass); + bool BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes); + bool BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los); + bool BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los); + bool BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell); + bool BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass); + bool BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); + bool BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes); + bool BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los); + bool BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid); + bool BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los); + bool BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid); + bool BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell); + bool BotCastCombatSong(Mob* tar, uint8 botLevel); + bool BotCastSong(Mob* tar, uint8 botLevel); + + bool CheckIfIncapacitated(); + bool IsAIProcessValid(const Client* bot_owner, const Group* bot_group, const Raid* raid); + + Client* SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint32 r_group) const; + Mob* SetFollowMob(Client* leash_owner); + + Mob* GetBotTarget(Client* bot_owner); + void AcquireBotTarget(Group* bot_group, Raid* raid, Client* leash_owner, float leash_distance); + void SetBotTarget(Client* bot_owner, Raid* raid, Group* bot_group, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat); + void SetLeashOwnerTarget(Client* leash_owner, Client* bot_owner, float lo_distance, float leash_distance); + void SetOwnerTarget(Client* bot_owner); + void SetBotGroupTarget(const Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, Mob* const& bg_member, Mob* bgm_target); + bool IsValidTarget(Client* bot_owner, Client* leash_owner, float lo_distance, float leash_distance, bool bo_alt_combat, Mob* tar, float tar_distance); + + bool PullingFlagChecks(Client* bot_owner); + bool ReturningFlagChecks(Client* bot_owner, float fm_distance); + void BotPullerProcess(Client* bot_owner, Raid* raid); + + + // Movement Methods + void CalcMeleeDistances( + const Mob* tar, + const EQ::ItemInstance* const& p_item, + const EQ::ItemInstance* const& s_item, + bool behind_mob, + bool backstab_weapon, + float& melee_distance_max, + float& melee_distance + ) const; + + // Combat Checks + void SetBerserkState(); + bool CheckIfCasting(float fm_distance); + void HealRotationChecks(); + void CheckCombatRange(Mob* tar, float tar_distance, bool& atCombatRange, const EQ::ItemInstance*& p_item, const EQ::ItemInstance*& s_item); + + // Try Combat Methods + bool TryEvade(Mob* tar); + bool TryFacingTarget(Mob* tar); + bool TryRangedAttack(Mob* tar); + bool TryClassAttacks(Mob* tar); + bool TryPrimaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* p_item); + bool TrySecondaryWeaponAttacks(Mob* tar, const EQ::ItemInstance* s_item); + bool TryPursueTarget(float leash_distance, glm::vec3& Goal); + bool TryMeditate(); + bool TryAutoDefend(Client* bot_owner, float leash_distance); + bool TryIdleChecks(float fm_distance); + bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal); + bool TryBardMovementCasts(); + void SetRangerCombatWeapon(bool atArcheryRange); + + // Public "Refactor" Methods + static bool CheckSpawnConditions(Client* c); protected: - void PetAIProcess(); void BotMeditate(bool isSitting); bool CheckBotDoubleAttack(bool Triple = false); void PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client* client); - bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0) override; + bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = nullptr) override; BotCastingRoles& GetCastingRoles() { return m_CastingRoles; } void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; } void SetGroupSlower(bool flag = true) { m_CastingRoles.GroupSlower = flag; } void SetGroupNuker(bool flag = true) { m_CastingRoles.GroupNuker = flag; } void SetGroupDoter(bool flag = true) { m_CastingRoles.GroupDoter = flag; } - //void SetRaidHealer(bool flag = true) { m_CastingRoles.RaidHealer = flag; } - //void SetRaidSlower(bool flag = true) { m_CastingRoles.RaidSlower = flag; } - //void SetRaidNuker(bool flag = true) { m_CastingRoles.RaidNuker = flag; } - //void SetRaidDoter(bool flag = true) { m_CastingRoles.RaidDoter = flag; } std::deque bot_signal_q; std::vector AIBot_spells; @@ -801,8 +871,7 @@ private: Timer m_evade_timer; // can be moved to pTimers at some point Timer m_alt_combat_hate_timer; Timer m_auto_defend_timer; - //Timer m_combat_jitter_timer; - //bool m_combat_jitter_flag; + bool m_dirtyautohaters; bool m_guard_flag; bool m_hold_flag; bool m_attack_flag; @@ -849,15 +918,12 @@ private: // Class Methods void LoadAAs(); - int32 acmod(); void GenerateBaseStats(); void GenerateAppearance(); - void GenerateArmorClass(); int32 GenerateBaseHitPoints(); int32 GenerateBaseManaPoints(); void GenerateSpecialAttacks(); void SetBotID(uint32 botID); - //void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; } void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; } void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; } void SetReturningFlag(bool flag = true) { m_returning_flag = flag; } @@ -870,9 +936,6 @@ private: bool LoadPet(); // Load and spawn bot pet if there is one bool SavePet(); // Save and depop bot pet if there is one bool DeletePet(); - - public: - static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND]; }; bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID); diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index d35597a00..f0c4827ce 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -133,81 +133,83 @@ public: if ( spells[spell_id].target_type != ST_Target && spells[spell_id].cast_restriction != 0 - ) { + ) { continue; } auto target_type = BCEnum::TT_None; switch (spells[spell_id].target_type) { - case ST_GroupTeleport: - target_type = BCEnum::TT_GroupV1; - break; - case ST_AECaster: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AECaster; - break; - case ST_AEBard: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AEBard; - break; - case ST_Target: - switch (spells[spell_id].cast_restriction) { - case 0: - target_type = BCEnum::TT_Single; + case ST_GroupTeleport: + target_type = BCEnum::TT_GroupV1; break; - case 104: + case ST_AECaster: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AECaster; + break; + case ST_AEBard: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AEBard; + break; + case ST_Target: + switch (spells[spell_id].cast_restriction) { + case 0: + target_type = BCEnum::TT_Single; + break; + case 104: + target_type = BCEnum::TT_Animal; + break; + case 105: + target_type = BCEnum::TT_Plant; + break; + case 118: + target_type = BCEnum::TT_Summoned; + break; + case 120: + target_type = BCEnum::TT_Undead; + break; + default: + break; + } + break; + case ST_Self: + target_type = BCEnum::TT_Self; + break; + case ST_AETarget: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AETarget; + break; + case ST_Animal: target_type = BCEnum::TT_Animal; break; - case 105: - target_type = BCEnum::TT_Plant; + case ST_Undead: + target_type = BCEnum::TT_Undead; break; - case 118: + case ST_Summoned: target_type = BCEnum::TT_Summoned; break; - case 120: - target_type = BCEnum::TT_Undead; + case ST_Corpse: + target_type = BCEnum::TT_Corpse; + break; + case ST_Plant: + target_type = BCEnum::TT_Plant; + break; + case ST_Group: + target_type = BCEnum::TT_GroupV2; break; default: break; - } - break; - case ST_Self: - target_type = BCEnum::TT_Self; - break; - case ST_AETarget: - // Disabled until bot code works correctly - //target_type = BCEnum::TT_AETarget; - break; - case ST_Animal: - target_type = BCEnum::TT_Animal; - break; - case ST_Undead: - target_type = BCEnum::TT_Undead; - break; - case ST_Summoned: - target_type = BCEnum::TT_Summoned; - break; - case ST_Corpse: - target_type = BCEnum::TT_Corpse; - break; - case ST_Plant: - target_type = BCEnum::TT_Plant; - break; - case ST_Group: - target_type = BCEnum::TT_GroupV2; - break; - default: - break; } if (target_type == BCEnum::TT_None) continue; - uint8 class_levels[16] = { 0 }; + uint8 class_levels[16] = {0}; bool player_spell = false; for (int class_type = WARRIOR; class_type <= BERSERKER; ++class_type) { int class_index = CLASSIDTOINDEX(class_type); - if (spells[spell_id].classes[class_index] == 0 || spells[spell_id].classes[class_index] > HARD_LEVEL_CAP) + if (spells[spell_id].classes[class_index] == 0 || + spells[spell_id].classes[class_index] > HARD_LEVEL_CAP) { continue; + } class_levels[class_index] = spells[spell_id].classes[class_index]; player_spell = true; @@ -218,124 +220,122 @@ public: STBaseEntry* entry_prototype = nullptr; while (true) { switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(1)]) { - case SE_BindAffinity: - entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); - break; - case SE_Charm: - if (spells[spell_id].spell_affect_index != 12) + case SE_BindAffinity: + entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); break; - entry_prototype = new STCharmEntry(); - if (spells[spell_id].resist_difficulty <= -1000) - entry_prototype->SafeCastToCharm()->dire = true; - break; - case SE_Teleport: - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - break; - case SE_Succor: - if (!strcmp(spells[spell_id].teleport_zone, "same")) { - entry_prototype = new STEscapeEntry; - } - else { + case SE_Charm: + if (spells[spell_id].spell_affect_index != 12) + break; + entry_prototype = new STCharmEntry(); + if (spells[spell_id].resist_difficulty <= -1000) + entry_prototype->SafeCastToCharm()->dire = true; + break; + case SE_Teleport: entry_prototype = new STDepartEntry; entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_Translocate: - if (spells[spell_id].teleport_zone[0] == '\0') { - entry_prototype = new STSendHomeEntry(); - entry_prototype->SafeCastToSendHome()->group = BCSpells::IsGroupType(target_type); - } - else { - entry_prototype = new STDepartEntry; - entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); - } - break; - case SE_ModelSize: - if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; - } - else if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 0 && spells[spell_id].base_value[EFFECTIDTOINDEX(1)] < 100) { - entry_prototype = new STSizeEntry; - entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; - } - break; - case SE_Identify: - entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); - break; - case SE_Invisibility: - if (spells[spell_id].spell_affect_index != 9) break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; - break; - case SE_SeeInvis: - if (spells[spell_id].spell_affect_index != 5) + case SE_Succor: + if (!strcmp(spells[spell_id].teleport_zone, "same")) { + entry_prototype = new STEscapeEntry; + } else { + entry_prototype = new STDepartEntry; + entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); + } break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; - break; - case SE_InvisVsUndead: - if (spells[spell_id].spell_affect_index != 9) + case SE_Translocate: + if (spells[spell_id].teleport_zone[0] == '\0') { + entry_prototype = new STSendHomeEntry(); + entry_prototype->SafeCastToSendHome()->group = BCSpells::IsGroupType(target_type); + } else { + entry_prototype = new STDepartEntry; + entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); + } break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; - break; - case SE_InvisVsAnimals: - if (spells[spell_id].spell_affect_index != 9) + case SE_ModelSize: + if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 100) { + entry_prototype = new STSizeEntry; + entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; + } else if (spells[spell_id].base_value[EFFECTIDTOINDEX(1)] > 0 && + spells[spell_id].base_value[EFFECTIDTOINDEX(1)] < 100) { + entry_prototype = new STSizeEntry; + entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; + } break; - entry_prototype = new STInvisibilityEntry; - entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; - break; - case SE_Mez: - if (spells[spell_id].spell_affect_index != 12) + case SE_Identify: + entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); - break; - case SE_Revive: - if (spells[spell_id].spell_affect_index != 1) + case SE_Invisibility: + if (spells[spell_id].spell_affect_index != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; break; - entry_prototype = new STResurrectEntry(); - entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); - break; - case SE_Rune: - if (spells[spell_id].spell_affect_index != 2) + case SE_SeeInvis: + if (spells[spell_id].spell_affect_index != 5) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; + break; + case SE_InvisVsUndead: + if (spells[spell_id].spell_affect_index != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; + break; + case SE_InvisVsAnimals: + if (spells[spell_id].spell_affect_index != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; + break; + case SE_Mez: + if (spells[spell_id].spell_affect_index != 12) + break; + entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); + break; + case SE_Revive: + if (spells[spell_id].spell_affect_index != 1) + break; + entry_prototype = new STResurrectEntry(); + entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); + break; + case SE_Rune: + if (spells[spell_id].spell_affect_index != 2) + break; + entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); + break; + case SE_SummonCorpse: + entry_prototype = new STBaseEntry(BCEnum::SpT_SummonCorpse); + break; + case SE_WaterBreathing: + entry_prototype = new STBaseEntry(BCEnum::SpT_WaterBreathing); + break; + default: break; - entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); - break; - case SE_SummonCorpse: - entry_prototype = new STBaseEntry(BCEnum::SpT_SummonCorpse); - break; - case SE_WaterBreathing: - entry_prototype = new STBaseEntry(BCEnum::SpT_WaterBreathing); - break; - default: - break; } if (entry_prototype) break; switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(2)]) { - case SE_Succor: - entry_prototype = new STEscapeEntry; - std::string is_lesser = spells[spell_id].name; - if (is_lesser.find("Lesser") != std::string::npos) - entry_prototype->SafeCastToEscape()->lesser = true; - break; + case SE_Succor: + entry_prototype = new STEscapeEntry; + std::string is_lesser = spells[spell_id].name; + if (is_lesser.find("Lesser") != std::string::npos) + entry_prototype->SafeCastToEscape()->lesser = true; + break; } if (entry_prototype) break; switch (spells[spell_id].effect_id[EFFECTIDTOINDEX(3)]) { - case SE_Lull: - entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); - break; - case SE_Levitate: // needs more criteria - entry_prototype = new STBaseEntry(BCEnum::SpT_Levitation); - break; - default: - break; + case SE_Lull: + entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); + break; + case SE_Levitate: // needs more criteria + entry_prototype = new STBaseEntry(BCEnum::SpT_Levitation); + break; + default: + break; } if (entry_prototype) break; @@ -343,7 +343,8 @@ public: while (spells[spell_id].type_description_id == 27) { if (!spells[spell_id].good_effect) break; - if (spells[spell_id].skill != EQ::skills::SkillOffense && spells[spell_id].skill != EQ::skills::SkillDefense) + if (spells[spell_id].skill != EQ::skills::SkillOffense && + spells[spell_id].skill != EQ::skills::SkillDefense) break; entry_prototype = new STStanceEntry(); @@ -358,98 +359,111 @@ public: break; switch (spells[spell_id].spell_affect_index) { - case 1: { - bool valid_spell = false; - entry_prototype = new STCureEntry; + case 1: { + bool valid_spell = false; + entry_prototype = new STCureEntry; - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].effect_id[effect_index] != SE_Blind && spells[spell_id].base_value[effect_index] >= 0) - continue; - else if (spells[spell_id].effect_id[effect_index] == SE_Blind && !spells[spell_id].good_effect) - continue; + for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { + int effect_index = EFFECTIDTOINDEX(i); + if (spells[spell_id].effect_id[effect_index] != SE_Blind && + spells[spell_id].base_value[effect_index] >= 0) + continue; + else if (spells[spell_id].effect_id[effect_index] == SE_Blind && + !spells[spell_id].good_effect) + continue; - switch (spells[spell_id].effect_id[effect_index]) { - case SE_Blind: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Blindness)] += spells[spell_id].base_value[effect_index]; - break; - case SE_DiseaseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Disease)] += spells[spell_id].base_value[effect_index]; - break; - case SE_PoisonCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Poison)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CurseCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Curse)] += spells[spell_id].base_value[effect_index]; - break; - case SE_CorruptionCounter: - entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Corruption)] += spells[spell_id].base_value[effect_index]; - break; - default: - continue; + switch (spells[spell_id].effect_id[effect_index]) { + case SE_Blind: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( + BCEnum::AT_Blindness)] += spells[spell_id].base_value[effect_index]; + break; + case SE_DiseaseCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( + BCEnum::AT_Disease)] += spells[spell_id].base_value[effect_index]; + break; + case SE_PoisonCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( + BCEnum::AT_Poison)] += spells[spell_id].base_value[effect_index]; + break; + case SE_CurseCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( + BCEnum::AT_Curse)] += spells[spell_id].base_value[effect_index]; + break; + case SE_CorruptionCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX( + BCEnum::AT_Corruption)] += spells[spell_id].base_value[effect_index]; + break; + default: + continue; + } + entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base_value[effect_index]; + valid_spell = true; } - entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - - break; - } - case 2: { - bool valid_spell = false; - entry_prototype = new STResistanceEntry; - - for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { - int effect_index = EFFECTIDTOINDEX(i); - if (spells[spell_id].base_value[effect_index] <= 0) - continue; - - switch (spells[spell_id].effect_id[effect_index]) { - case SE_ResistFire: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Fire)] += spells[spell_id].base_value[effect_index]; - break; - case SE_ResistCold: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Cold)] += spells[spell_id].base_value[effect_index]; - break; - case SE_ResistPoison: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Poison)] += spells[spell_id].base_value[effect_index]; - break; - case SE_ResistDisease: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Disease)] += spells[spell_id].base_value[effect_index]; - break; - case SE_ResistMagic: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Magic)] += spells[spell_id].base_value[effect_index]; - break; - case SE_ResistCorruption: - entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Corruption)] += spells[spell_id].base_value[effect_index]; - break; - default: - continue; + if (!valid_spell) { + safe_delete(entry_prototype); + entry_prototype = nullptr; } - entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base_value[effect_index]; - valid_spell = true; - } - if (!valid_spell) { - safe_delete(entry_prototype); - entry_prototype = nullptr; - } - break; - } - case 7: - case 10: - if (spells[spell_id].effect_description_id != 65) break; - if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) + } + case 2: { + bool valid_spell = false; + entry_prototype = new STResistanceEntry; + + for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { + int effect_index = EFFECTIDTOINDEX(i); + if (spells[spell_id].base_value[effect_index] <= 0) + continue; + + switch (spells[spell_id].effect_id[effect_index]) { + case SE_ResistFire: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Fire)] += spells[spell_id].base_value[effect_index]; + break; + case SE_ResistCold: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Cold)] += spells[spell_id].base_value[effect_index]; + break; + case SE_ResistPoison: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Poison)] += spells[spell_id].base_value[effect_index]; + break; + case SE_ResistDisease: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Disease)] += spells[spell_id].base_value[effect_index]; + break; + case SE_ResistMagic: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Magic)] += spells[spell_id].base_value[effect_index]; + break; + case SE_ResistCorruption: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX( + BCEnum::RT_Corruption)] += spells[spell_id].base_value[effect_index]; + break; + default: + continue; + } + entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base_value[effect_index]; + valid_spell = true; + } + if (!valid_spell) { + safe_delete(entry_prototype); + entry_prototype = nullptr; + } + + break; + } + case 7: + case 10: + if (spells[spell_id].effect_description_id != 65) + break; + if (IsEffectInSpell(spell_id, SE_NegateIfCombat)) + break; + entry_prototype = new STMovementSpeedEntry(); + entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); + break; + default: break; - entry_prototype = new STMovementSpeedEntry(); - entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); - break; - default: - break; } if (entry_prototype) break; @@ -459,7 +473,8 @@ public: if (!entry_prototype) continue; - if (target_type == BCEnum::TT_Self && (entry_prototype->BCST() != BCEnum::SpT_Stance && entry_prototype->BCST() != BCEnum::SpT_SummonCorpse)) { + if (target_type == BCEnum::TT_Self && (entry_prototype->BCST() != BCEnum::SpT_Stance && + entry_prototype->BCST() != BCEnum::SpT_SummonCorpse)) { #ifdef BCSTSPELLDUMP LogError("DELETING entry_prototype (primary clause) - name: [{}], target_type: [{}], BCST: [{}]", spells[spell_id].name, BCEnum::TargetTypeEnumToString(target_type).c_str(), BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str()); @@ -489,53 +504,53 @@ public: STBaseEntry* spell_entry = nullptr; switch (entry_prototype->BCST()) { - case BCEnum::SpT_Charm: - if (entry_prototype->IsCharm()) - spell_entry = new STCharmEntry(entry_prototype->SafeCastToCharm()); - break; - case BCEnum::SpT_Cure: - if (entry_prototype->IsCure()) - spell_entry = new STCureEntry(entry_prototype->SafeCastToCure()); - break; - case BCEnum::SpT_Depart: - if (entry_prototype->IsDepart()) - spell_entry = new STDepartEntry(entry_prototype->SafeCastToDepart()); - break; - case BCEnum::SpT_Escape: - if (entry_prototype->IsEscape()) - spell_entry = new STEscapeEntry(entry_prototype->SafeCastToEscape()); - break; - case BCEnum::SpT_Invisibility: - if (entry_prototype->IsInvisibility()) - spell_entry = new STInvisibilityEntry(entry_prototype->SafeCastToInvisibility()); - break; - case BCEnum::SpT_MovementSpeed: - if (entry_prototype->IsMovementSpeed()) - spell_entry = new STMovementSpeedEntry(entry_prototype->SafeCastToMovementSpeed()); - break; - case BCEnum::SpT_Resistance: - if (entry_prototype->IsResistance()) - spell_entry = new STResistanceEntry(entry_prototype->SafeCastToResistance()); - break; - case BCEnum::SpT_Resurrect: - if (entry_prototype->IsResurrect()) - spell_entry = new STResurrectEntry(entry_prototype->SafeCastToResurrect()); - break; - case BCEnum::SpT_SendHome: - if (entry_prototype->IsSendHome()) - spell_entry = new STSendHomeEntry(entry_prototype->SafeCastToSendHome()); - break; - case BCEnum::SpT_Size: - if (entry_prototype->IsSize()) - spell_entry = new STSizeEntry(entry_prototype->SafeCastToSize()); - break; - case BCEnum::SpT_Stance: - if (entry_prototype->IsStance()) - spell_entry = new STStanceEntry(entry_prototype->SafeCastToStance()); - break; - default: - spell_entry = new STBaseEntry(entry_prototype); - break; + case BCEnum::SpT_Charm: + if (entry_prototype->IsCharm()) + spell_entry = new STCharmEntry(entry_prototype->SafeCastToCharm()); + break; + case BCEnum::SpT_Cure: + if (entry_prototype->IsCure()) + spell_entry = new STCureEntry(entry_prototype->SafeCastToCure()); + break; + case BCEnum::SpT_Depart: + if (entry_prototype->IsDepart()) + spell_entry = new STDepartEntry(entry_prototype->SafeCastToDepart()); + break; + case BCEnum::SpT_Escape: + if (entry_prototype->IsEscape()) + spell_entry = new STEscapeEntry(entry_prototype->SafeCastToEscape()); + break; + case BCEnum::SpT_Invisibility: + if (entry_prototype->IsInvisibility()) + spell_entry = new STInvisibilityEntry(entry_prototype->SafeCastToInvisibility()); + break; + case BCEnum::SpT_MovementSpeed: + if (entry_prototype->IsMovementSpeed()) + spell_entry = new STMovementSpeedEntry(entry_prototype->SafeCastToMovementSpeed()); + break; + case BCEnum::SpT_Resistance: + if (entry_prototype->IsResistance()) + spell_entry = new STResistanceEntry(entry_prototype->SafeCastToResistance()); + break; + case BCEnum::SpT_Resurrect: + if (entry_prototype->IsResurrect()) + spell_entry = new STResurrectEntry(entry_prototype->SafeCastToResurrect()); + break; + case BCEnum::SpT_SendHome: + if (entry_prototype->IsSendHome()) + spell_entry = new STSendHomeEntry(entry_prototype->SafeCastToSendHome()); + break; + case BCEnum::SpT_Size: + if (entry_prototype->IsSize()) + spell_entry = new STSizeEntry(entry_prototype->SafeCastToSize()); + break; + case BCEnum::SpT_Stance: + if (entry_prototype->IsStance()) + spell_entry = new STStanceEntry(entry_prototype->SafeCastToStance()); + break; + default: + spell_entry = new STBaseEntry(entry_prototype); + break; } assert(spell_entry); @@ -545,7 +560,8 @@ public: bot_command_spells[spell_entry->BCST()].push_back(spell_entry); - if (bot_levels.find(class_type) == bot_levels.end() || bot_levels[class_type] > class_levels[class_index]) + if (bot_levels.find(class_type) == bot_levels.end() || + bot_levels[class_type] > class_levels[class_index]) bot_levels[class_type] = class_levels[class_index]; } @@ -561,15 +577,15 @@ public: #ifdef BCSTSPELLDUMP spell_dump(); #endif - } static void Unload() { for (auto map_iter : bot_command_spells) { if (map_iter.second.empty()) continue; - for (auto list_iter : map_iter.second) - safe_delete(list_iter); + for (auto list_iter: map_iter.second) { + safe_delete(list_iter); + } map_iter.second.clear(); } bot_command_spells.clear(); @@ -579,24 +595,23 @@ public: static bool IsCasterCentered(BCEnum::TType target_type) { switch (target_type) { - case BCEnum::TT_AECaster: - case BCEnum::TT_AEBard: - return true; - default: - return false; + case BCEnum::TT_AECaster: + case BCEnum::TT_AEBard: + return true; + default: + return false; } } static bool IsGroupType(BCEnum::TType target_type) { switch (target_type) { - case BCEnum::TT_GroupV1: - case BCEnum::TT_GroupV2: - return true; - default: - return false; + case BCEnum::TT_GroupV1: + case BCEnum::TT_GroupV2: + return true; + default: + return false; } } - private: static void remove_inactive() { if (bot_command_spells.empty()) @@ -620,8 +635,10 @@ private: } }); - for (auto del_iter : *removed_spells_list) + for (auto del_iter: *removed_spells_list) + { safe_delete(del_iter); + } removed_spells_list->clear(); if (RuleI(Bots, CommandSpellRank) == 1) { @@ -645,8 +662,9 @@ private: return false; }); - for (auto del_iter : *removed_spells_list) + for (auto del_iter: *removed_spells_list) { safe_delete(del_iter); + } removed_spells_list->clear(); } @@ -669,8 +687,9 @@ private: return false; }); - for (auto del_iter : *removed_spells_list) + for (auto del_iter: *removed_spells_list) { safe_delete(del_iter); + } removed_spells_list->clear(); } @@ -696,8 +715,9 @@ private: return false; }); - for (auto del_iter : *removed_spells_list) + for (auto del_iter: *removed_spells_list) { safe_delete(del_iter); + } removed_spells_list->clear(); } @@ -1255,6 +1275,8 @@ int bot_command_count; // how many bot commands we have // init has been performed to point at the real function int (*bot_command_dispatch)(Client *,char const *) = bot_command_not_avail; + + std::map bot_command_list; std::map bot_command_aliases; @@ -1926,7 +1948,7 @@ namespace MyBots if (!clear_list) UniquifySBL(sbl); } -}; +} namespace ActionableTarget { @@ -2120,7 +2142,7 @@ namespace ActionableTarget return target[target_type]; } }; -}; +} namespace ActionableBots { @@ -2461,7 +2483,7 @@ namespace ActionableBots ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, EQ::skills::SkillPickLock, pick_lock_value); } -}; +} /* @@ -2817,7 +2839,7 @@ void bot_command_bind_affinity(Client *c, const Seperator *sep) void bot_command_bot(Client *c, const Seperator *sep) { - /* VS2012 code - begin */ + std::list subcommand_list; subcommand_list.push_back("botappearance"); subcommand_list.push_back("botcamp"); @@ -2838,14 +2860,6 @@ void bot_command_bot(Client *c, const Seperator *sep) subcommand_list.push_back("bottogglearcher"); subcommand_list.push_back("bottogglehelm"); subcommand_list.push_back("botupdate"); - /* VS2012 code - end */ - - /* VS2013 code - const std::list subcommand_list = { - "botappearance", "botcamp", "botclone", "botcreate", "botdelete", "botdetails", "botdyearmor", "botinspectmessage", "botfollowdistance", - "botlist", "botoutofcombat", "botreport", "botspawn", "botstance", "botsummon", "bottogglearcher", "bottogglehelm", "botupdate" - }; - */ if (helper_command_alias_fail(c, "bot_command_bot", sep->arg[0], "bot")) return; @@ -3352,7 +3366,7 @@ void bot_command_guard(Client *c, const Seperator *sep) void bot_command_heal_rotation(Client *c, const Seperator *sep) { - /* VS2012 code - begin */ + std::list subcommand_list; subcommand_list.push_back("healrotationadaptivetargeting"); subcommand_list.push_back("healrotationaddmember"); @@ -3374,16 +3388,6 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep) subcommand_list.push_back("healrotationsethot"); subcommand_list.push_back("healrotationstart"); subcommand_list.push_back("healrotationstop"); - /* VS2012 code - end */ - - /* VS2013 code - const std::list subcommand_list = { - "healrotationadaptivetargeting", "healrotationaddmember", "healrotationaddtarget", "healrotationadjustcritical", "healrotationadjustsafe", - "healrotationcastoverride", "healrotationchangeinterval", "healrotationclearhot", "healrotationcleartargets", "healrotationcreate", - "healrotationdelete", "healrotationfastheals", "healrotationlist", "healrotationremovemember", "healrotationremovetarget", "healrotationsave", - "healrotationresetlimits", "healrotationsethot", "healrotationstart", "healrotationstop" - }; - */ if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation")) return; @@ -3540,17 +3544,12 @@ void bot_command_identify(Client *c, const Seperator *sep) void bot_command_inventory(Client *c, const Seperator *sep) { - /* VS2012 code - begin */ + std::list subcommand_list; subcommand_list.push_back("inventorygive"); subcommand_list.push_back("inventorylist"); subcommand_list.push_back("inventoryremove"); subcommand_list.push_back("inventorywindow"); - /* VS2012 code - end */ - - /* VS2013 code - const std::list subcommand_list = { "inventorygive", "inventorylist", "inventoryremove", "inventorywindow" }; - */ if (helper_command_alias_fail(c, "bot_command_inventory", sep->arg[0], "inventory")) return; @@ -4015,7 +4014,7 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booDeathMarquee, c->GetBotOption(Client::booDeathMarquee)); - c->Message(Chat::White, "Bot 'death marquee' is now %s.", (c->GetBotOption(Client::booDeathMarquee) == true ? "enabled" : "disabled")); + c->Message(Chat::White, "Bot 'death marquee' is now %s.", (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled")); } else if (!owner_option.compare("statsupdate")) { @@ -4031,7 +4030,7 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booStatsUpdate, c->GetBotOption(Client::booStatsUpdate)); - c->Message(Chat::White, "Bot 'stats update' is now %s.", (c->GetBotOption(Client::booStatsUpdate) == true ? "enabled" : "disabled")); + c->Message(Chat::White, "Bot 'stats update' is now %s.", (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled")); } else if (!owner_option.compare("spawnmessage")) { @@ -4117,7 +4116,7 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAltCombat, c->GetBotOption(Client::booAltCombat)); - c->Message(Chat::White, "Bot 'alt combat' is now %s.", (c->GetBotOption(Client::booAltCombat) == true ? "enabled" : "disabled")); + c->Message(Chat::White, "Bot 'alt combat' is now %s.", (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled")); } else { c->Message(Chat::White, "Bot owner option 'altcombat' is not allowed on this server."); @@ -4139,7 +4138,7 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAutoDefend, c->GetBotOption(Client::booAutoDefend)); - c->Message(Chat::White, "Bot 'auto defend' is now %s.", (c->GetBotOption(Client::booAutoDefend) == true ? "enabled" : "disabled")); + c->Message(Chat::White, "Bot 'auto defend' is now %s.", (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled")); } else { c->Message(Chat::White, "Bot owner option 'autodefend' is not allowed on this server."); @@ -4159,7 +4158,7 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booBuffCounter, c->GetBotOption(Client::booBuffCounter)); - c->Message(Chat::White, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled")); + c->Message(Chat::White, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")); } else if (!owner_option.compare("monkwumessage")) { @@ -4175,7 +4174,11 @@ void bot_command_owner_option(Client *c, const Seperator *sep) database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage)); - c->Message(Chat::White, "Bot 'monk wu message' is now %s.", (c->GetBotOption(Client::booMonkWuMessage) == true ? "enabled" : "disabled")); + c->Message( + Chat::White, + "Bot 'monk wu message' is now %s.", + (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled") + ); } else if (!owner_option.compare("current")) { @@ -4214,16 +4217,11 @@ void bot_command_owner_option(Client *c, const Seperator *sep) void bot_command_pet(Client *c, const Seperator *sep) { - /* VS2012 code - begin */ + std::list subcommand_list; subcommand_list.push_back("petgetlost"); subcommand_list.push_back("petremove"); subcommand_list.push_back("petsettype"); - /* VS2012 code - end */ - - /* VS2013 code - const std::list subcommand_list = { "petgetlost", "petremove", "petsettype" }; - */ if (helper_command_alias_fail(c, "bot_command_pet", sep->arg[0], "pet")) return; @@ -4317,7 +4315,7 @@ void bot_command_precombat(Client* c, const Seperator* sep) c->SetBotPrecombat(!c->GetBotPrecombat()); } - c->Message(Chat::White, "Precombat flag is now %s.", (c->GetBotPrecombat() == true ? "set" : "clear")); + c->Message(Chat::White, "Precombat flag is now %s.", (c->GetBotPrecombat() ? "set" : "clear")); } // TODO: Rework to allow owner specificed criteria for puller @@ -5014,7 +5012,7 @@ void bot_command_water_breathing(Client *c, const Seperator *sep) */ void bot_subcommand_bot_appearance(Client *c, const Seperator *sep) { - /* VS2012 code - begin */ + std::list subcommand_list; subcommand_list.push_back("botbeardcolor"); subcommand_list.push_back("botbeardstyle"); @@ -5026,14 +5024,6 @@ void bot_subcommand_bot_appearance(Client *c, const Seperator *sep) subcommand_list.push_back("botheritage"); subcommand_list.push_back("bottattoo"); subcommand_list.push_back("botwoad"); - /* VS2012 code - end */ - - /* VS2013 code - const std::list subcommand_list = { - "botbeardcolor", "botbeardstyle", "botdetails", "boteyes", "botface", - "bothaircolor", "bothairstyle", "botheritage", "bottattoo", "botwoad" - }; - */ if (helper_command_alias_fail(c, "bot_subcommand_bot_appearance", sep->arg[0], "botappearance")) return; @@ -6539,8 +6529,7 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) return; } - if (c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn a bot while feigned."); + if (!Bot::CheckSpawnConditions(c)) { return; } @@ -6654,39 +6643,6 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) return; } - // this probably needs work... - if (c->GetGroup()) { - std::list group_list; - c->GetGroup()->GetMemberList(group_list); - for (auto member_iter : group_list) { - if (!member_iter) { - continue; - } - - if (member_iter->qglobal) { // what is this?? really should have had a message to describe failure... (can't spawn bots if you are assigned to a task/instance?) - return; - } - - if ( - !member_iter->qglobal && - member_iter->GetAppearance() != eaDead && - ( - member_iter->IsEngaged() || - ( - member_iter->IsClient() && - member_iter->CastToClient()->GetAggroCount() - ) - ) - ) { - c->Message(Chat::White, "You cannot summon bots while you are engaged."); - return; - } - } - } else if (c->GetAggroCount()) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged."); - return; - } - auto my_bot = Bot::LoadBot(bot_id); if (!my_bot) { c->Message( @@ -7831,30 +7787,10 @@ void bot_subcommand_botgroup_load(Client *c, const Seperator *sep) return; } - if (c->GetFeigned()) { - c->Message(Chat::White, "You cannot spawn a bot-group while feigned."); + if (!Bot::CheckSpawnConditions(c)) { return; } - auto* owner_group = c->GetGroup(); - if (owner_group) { - std::list member_list; - owner_group->GetClientList(member_list); - member_list.remove(nullptr); - - for (auto member_iter : member_list) { - if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while your group is engaged,"); - return; - } - } - } else { - if (c->GetAggroCount() > 0) { - c->Message(Chat::White, "You cannot spawn bots while you are engaged,"); - return; - } - } - uint32 botgroup_id = 0; if (!database.botdb.LoadBotGroupIDForLoadBotGroup(c->CharacterID(), botgroup_name, botgroup_id) || !botgroup_id) { c->Message( @@ -10100,7 +10036,7 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command) { - if (rule_value == false) { + if (rule_value) { bot_owner->Message(Chat::White, "Bot command %s is not enabled on this server.", command); return true; } @@ -10618,7 +10554,7 @@ void bot_command_spell_settings_toggle(Client *c, const Seperator *sep) bool toggle = ( sep->IsNumber(2) ? - (Strings::ToInt(sep->arg[2]) ? true : false) : + Strings::ToInt(sep->arg[2]) != 0 : atobool(sep->arg[2]) ); diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp new file mode 100644 index 000000000..22edf3f76 --- /dev/null +++ b/zone/bot_raid.cpp @@ -0,0 +1,287 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bot.h" +#include "object.h" +#include "raids.h" +#include "doors.h" +#include "quest_parser_collection.h" +#include "../common/data_verification.h" + +extern volatile bool is_zone_loaded; +extern bool Critical; + +std::vector Raid::GetRaidGroupMembers(uint32 gid) +{ + std::vector raid_group_members; + raid_group_members.clear(); + + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + { + if (members[i].member && members[i].GroupNumber == gid) + { + raid_group_members.push_back(members[i]); + } + } + return raid_group_members; +} + +// Returns Bot members that are in the raid +// passing in owner will return all Bots that have the same owner. +std::vector Raid::GetRaidBotMembers(uint32 owner) +{ + std::vector raid_members_bots; + raid_members_bots.clear(); + + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if ( + members[i].member && + members[i].member->IsBot() + ) { + auto b_member = members[i].member->CastToBot(); + if (owner && b_member->GetBotOwnerCharacterID() == owner) { + raid_members_bots.emplace_back(b_member); + } else if (!owner) { + raid_members_bots.emplace_back(b_member); + } + } + } + + return raid_members_bots; +} + +// Returns Bot members that are in the group specified +// passing in owner will return only Bots that have the same owner. +std::vector Raid::GetRaidGroupBotMembers(uint32 gid) +{ + std::vector raid_members_bots; + raid_members_bots.clear(); + + for (int i = 0; i < MAX_RAID_MEMBERS; i++) { + if ( + members[i].member && + members[i].member->IsBot() && + members[i].GroupNumber == gid + ) { + auto b_member = members[i].member->CastToBot(); + raid_members_bots.emplace_back(b_member); + raid_members_bots.emplace_back(b_member); + } + } + + return raid_members_bots; +} + +void Raid::HandleBotGroupDisband(uint32 owner, uint32 gid) +{ + auto raid_members_bots = gid != RAID_GROUPLESS ? GetRaidGroupBotMembers(gid) : GetRaidBotMembers(owner); + + // If any of the bots are a group leader then re-create the botgroup on disband, dropping any clients + for (auto& bot_iter: raid_members_bots) { + + // Remove the entire BOT group in this case + if ( + bot_iter && + gid != RAID_GROUPLESS && + IsRaidMember(bot_iter->GetName()) && + IsGroupLeader(bot_iter->GetName()) + ) { + auto r_group_members = GetRaidGroupMembers(GetGroup(bot_iter->GetName())); + auto group_inst = new Group(bot_iter); + entity_list.AddGroup(group_inst); + database.SetGroupID(bot_iter->GetCleanName(), group_inst->GetID(), bot_iter->GetBotID()); + database.SetGroupLeaderName(group_inst->GetID(), bot_iter->GetName()); + + for (auto member_iter: r_group_members) { + if (member_iter.member->IsBot()) { + auto b_member = member_iter.member->CastToBot(); + if (strcmp(b_member->GetName(), bot_iter->GetName()) == 0) { + bot_iter->SetFollowID(owner); + } else { + Bot::AddBotToGroup(b_member, group_inst); + } + Bot::RemoveBotFromRaid(b_member); + } + } + } else { + Bot::RemoveBotFromRaid(bot_iter); + } + } +} + +uint8 Bot::GetNumberNeedingHealedInRaidGroup(uint8& need_healed, uint8 hpr, bool includePets, Raid* raid) { + + if (raid) { + uint32 r_group = raid->GetGroup(GetName()); + auto raid_group_members = raid->GetRaidGroupMembers(r_group); + + for (auto& m: raid_group_members) { + if (m.member && !m.member->qglobal) { + if (m.member->GetHPRatio() <= hpr) { + need_healed++; + } + + if (includePets) { + if (m.member->GetPet() && m.member->GetPet()->GetHPRatio() <= hpr) { + need_healed++; + } + } + } + } + } + return need_healed; +} + +void Bot::ProcessRaidInvite(Mob* invitee, Client* invitor, bool group_invite) { + + if (!invitee || !invitor) { + return; + } + + if (invitee->IsBot() && + invitor->HasRaid() && + invitee->GetOwner()->HasRaid() && + invitor->GetRaid()->IsRaidMember(invitee->GetOwner()->GetName()) + ) { + // If the Bot Owner is in our raid we need to be able to invite their Bots + } + else if (invitee->IsBot() && (invitee->CastToBot()->GetBotOwnerCharacterID() != invitor->CharacterID())) { + invitor->Message( + Chat::Red, + fmt::format( + "{} is not your Bot. You can only invite your own Bots, or Bots that belong to a Raid member.", + invitee->GetCleanName() + ).c_str() + ); + return; + } + + Raid* raid = entity_list.GetRaidByClient(invitor); + + Bot::CreateBotRaid(invitee, invitor, group_invite, raid); +} + +void Bot::CreateBotRaid(Mob* invitee, Client* invitor, bool group_invite, Raid* raid) { + + Group* g_invitee = invitee->GetGroup(); + Group* g_invitor = invitor->GetGroup(); + + if (g_invitee && invitor->IsClient()) { + if (!g_invitee->IsLeader(invitee)) { + invitor->Message( + Chat::Red, + fmt::format( + "You can only invite group leaders or ungrouped bots. Try {} instead.", + g_invitee->GetLeader()->GetCleanName() + ).c_str() + ); + return; + } + } + + bool new_raid = false; + if (!raid) { + new_raid = true; + raid = new Raid(invitor); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + } + + // Add Invitor to new raid + if (new_raid) { + if (g_invitor) { + ProcessBotGroupAdd(g_invitor, raid, nullptr, true, true); + } else { + raid->SendRaidCreate(invitor); + raid->AddMember(invitor, 0xFFFFFFFF, true, false, true); + raid->SendMakeLeaderPacketTo(invitor->GetName(), invitor); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + } + + // Add Bot Group or Client Bot Group to raid + if (g_invitee) { + ProcessBotGroupAdd(g_invitee, raid); + + // Add individual client to raid + } else if (invitee->IsClient()) { + ProcessBotGroupAdd(g_invitee, raid, invitee->CastToClient()); + + // Add individual bot to raid + } else { + auto gid = raid->GetGroup(invitor->GetName()); + auto b = invitee->CastToBot(); + + // gives us a choice to either invite directly into the clients Raid Group, or just into the Raid + if (group_invite && raid->GroupCount(gid) < MAX_GROUP_MEMBERS) { + raid->AddBot(b, gid); + } else { + raid->AddBot(b); + } + + if (new_raid) { + invitee->SetFollowID(invitor->GetID()); + } + } +} + +void Bot::ProcessBotGroupAdd(Group* group, Raid* raid, Client* client, bool new_raid, bool initial) { + + uint32 raid_free_group_id = raid->GetFreeGroup(); + if (group) { + for (int x = 0; x < MAX_GROUP_MEMBERS; x++) { + if (group->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + + if (group->members[x] && group->members[x]->IsBot()) { + b = group->members[x]->CastToBot(); + raid->AddBot(b, raid_free_group_id, false, x == 0, false); + } else if (group->members[x] && group->members[x]->IsClient()) { + c = group->members[x]->CastToClient(); + raid->SendRaidCreate(c); + raid->AddMember( + c, + raid_free_group_id, + new_raid, + x == 0, + false + ); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->SendBulkRaid(c); + } + } + } + group->JoinRaidXTarget(raid, initial); + group->DisbandGroup(true); + } else if (client) { + raid->SendRaidCreate(client); + raid->AddMember(client, RAID_GROUPLESS, false, false, true); + raid->SendMakeLeaderPacketTo(raid->leadername, client); + raid->SendBulkRaid(client); + if (raid->IsLocked()) { + raid->SendRaidLockTo(client); + } + } + + raid->GroupUpdate(raid_free_group_id); +} + + diff --git a/zone/bot_raid.h b/zone/bot_raid.h new file mode 100644 index 000000000..994b87e1b --- /dev/null +++ b/zone/bot_raid.h @@ -0,0 +1,41 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef BOT_RAID_H +#define BOT_RAID_H + + +#include "bot_structs.h" +#include "mob.h" +#include "client.h" +#include "pets.h" +#include "heal_rotation.h" +#include "groups.h" +#include "corpse.h" +#include "zonedb.h" +#include "../common/zone_store.h" +#include "string_ids.h" +#include "../common/misc_functions.h" +#include "../common/global_define.h" +#include "guild_mgr.h" +#include "worldserver.h" +#include "raids.h" + +#include + +#endif // BOT_RAID_H diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index c7f909d7c..54dc97543 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -22,16 +22,11 @@ #include "../common/repositories/bot_spells_entries_repository.h" #include "../common/repositories/npc_spells_repository.h" -#if EQDEBUG >= 12 - #define BotAI_DEBUG_Spells 25 -#elif EQDEBUG >= 9 - #define BotAI_DEBUG_Spells 10 -#else - #define BotAI_DEBUG_Spells -1 -#endif - bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { + // Bot AI + Raid* raid = entity_list.GetRaidByBotName(GetName()); + if (!tar) { return false; } @@ -40,19 +35,12 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { return false; } - if (iChance < 100) { - if (zone->random.Int(0, 100) > iChance) { - return false; - } + if (iChance < 100 && zone->random.Int(0, 100) > iChance) { + return false; } - if (tar->GetAppearance() == eaDead) { - if ((tar->IsClient() && tar->CastToClient()->GetFeigned()) || tar->IsBot()) { - // do nothing - } - else { - return false; - } + if (tar->GetAppearance() == eaDead && ((tar->IsClient() && !tar->CastToClient()->GetFeigned()) || tar->IsBot())) { + return false; } uint8 botClass = GetClass(); @@ -60,1143 +48,1225 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. - bool castedSpell = false; - BotSpell botSpell; botSpell.SpellId = 0; botSpell.SpellIndex = 0; botSpell.ManaCost = 0; switch (iSpellTypes) { - case SpellType_Mez: { - if (tar->GetBodyType() != BT_Giant) { - if (!checked_los) { - if (!CheckLosFN(tar)) { - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - } - checked_los = true; - } - - //TODO - //Check if single target or AoE mez is best - //if (TARGETS ON MT IS => 3 THEN botSpell = AoEMez) - //if (TARGETS ON MT IS <= 2 THEN botSpell = BestMez) - - botSpell = GetBestBotSpellForMez(this); - - if (botSpell.SpellId == 0) { - break; - } - - Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); - - if (!addMob) { - //Say("!addMob."); - break; - } - - if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - break; - } - - if (IsValidSpellRange(botSpell.SpellId, addMob)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); - } - if (castedSpell) { - BotGroupSay( - this, - fmt::format( - "Attempting to mesmerize {} with {}.", - addMob->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str() - ); - } - } - break; - } - case SpellType_Heal: { - if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { - uint8 hpr = (uint8)tar->GetHPRatio(); - bool hasAggro = false; - bool isPrimaryHealer = false; - - if (HasGroup()) { - isPrimaryHealer = IsGroupHealer(); - } - - if (hpr < 95 || (tar->IsClient() && (hpr < 95)) || (botClass == BARD)) { - if (tar->GetClass() == NECROMANCER) { - // Give necromancers a chance to go lifetap something or cleric can spend too much mana on a necro - if (hpr >= 40) { - break; - } - } - - if (tar->GetClass() == SHAMAN) { - // Give shaman the chance to canni without wasting the cleric's mana - if (hpr >= 80) { - break; - } - } - - // Evaluate the situation - if ((IsEngaged()) && ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN))) { - if (tar->GetTarget() && tar->GetTarget()->GetHateTop() && tar->GetTarget()->GetHateTop() == tar) { - hasAggro = true; - } - - if (hpr < 35) { - botSpell = GetBestBotSpellForFastHeal(this); - } - else if (hpr >= 35 && hpr < 70) { - if (GetNumberNeedingHealedInGroup(60, false) >= 3) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else if (hpr >= 70 && hpr < 95) { - if (GetNumberNeedingHealedInGroup(80, false) >= 3) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - if (hasAggro) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - else { - if (!tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); - } - } - } - else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) { - if (GetNumberNeedingHealedInGroup(40, true) >= 2) { - botSpell = GetBestBotSpellForGroupCompleteHeal(this); - - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForGroupHeal(this); - } - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - if (hpr < 40) { - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - } - else if (GetNumberNeedingHealedInGroup(60, true) >= 2) { - botSpell = GetBestBotSpellForGroupHeal(this); - - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForGroupHealOverTime(this); - } - if (hpr < 40) { - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - } - } - else if (hpr < 40) { - botSpell = GetBestBotSpellForPercentageHeal(this); - } - else if (hpr >= 40 && hpr < 75) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - else { - if (hpr < 90 && !tar->FindType(SE_HealOverTime)) { - botSpell = GetBestBotSpellForHealOverTime(this); - } - } - } - else { - float hpRatioToCast = 0.0f; - - switch(GetBotStance()) { - case EQ::constants::stanceEfficient: - case EQ::constants::stanceAggressive: - hpRatioToCast = isPrimaryHealer?90.0f:50.0f; - break; - case EQ::constants::stanceBalanced: - hpRatioToCast = isPrimaryHealer?95.0f:75.0f; - break; - case EQ::constants::stanceReactive: - hpRatioToCast = isPrimaryHealer?100.0f:90.0f; - break; - case EQ::constants::stanceBurn: - case EQ::constants::stanceBurnAE: - hpRatioToCast = isPrimaryHealer?75.0f:25.0f; - break; - default: - hpRatioToCast = isPrimaryHealer?100.0f:0.0f; - break; - } - - //If we're at specified mana % or below, don't heal as hybrid - if (tar->GetHPRatio() <= hpRatioToCast) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - } - - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - } - if (botSpell.SpellId == 0) { - botSpell = GetFirstBotSpellForSingleTargetHeal(this); - } - if (botSpell.SpellId == 0 && botClass == BARD) { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); - } - - // If there is still no spell id, then there isn't going to be one so we are done - if (botSpell.SpellId == 0) { - break; - } - // Can we cast this spell on this target? - if (!(spells[botSpell.SpellId].target_type==ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) - && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { - break; - } - - uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); - } - - if (castedSpell) { - /*if (TempDontHealMeBeforeTime != tar->DontHealMeBefore()) - tar->SetDontHealMeBefore(TempDontHealMeBeforeTime); - - // For non-HoT heals, do a 4 second delay - // TODO: Replace this code with logic that calculates the delay based on number of clerics in rotation - // and ignores heals for anyone except the main tank - if (!IsHealOverTimeSpell(botSpell.SpellId)) { - if (IsCompleteHealSpell(botSpell.SpellId)) { - // Complete Heal 4 second rotation - tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 4000); - } - else { - tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - }*/ - if (botClass != BARD) { - if (IsGroupSpell(botSpell.SpellId)) { - if (HasGroup()) { - Group *g = GetGroup(); - - if (g) { - BotGroupSay( - this, - fmt::format( - "Casting {}.", - spells[botSpell.SpellId].name - ).c_str() - ); - - for( int i = 0; imembers[i] && !g->members[i]->qglobal) { - g->members[i]->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); - } - } - } - } - } else { - if (tar != this) { //we don't need spam of bots healing themselves - BotGroupSay( - this, - fmt::format( - "Casting {} on {}.", - spells[botSpell.SpellId].name, - tar->GetCleanName() - ).c_str() - ); - } - - tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000); - } - } - } - } - } - break; - } - case SpellType_Root: { - if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los) { - if (!CheckLosFN(tar)) { - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - } - checked_los = true; - } - - // TODO: If there is a ranger in the group then don't allow root spells - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (botSpell.SpellId == 0) { - break; - } - if (tar->CanBuffStack(botSpell.SpellId, botLevel, true) == 0) { - break; - } - uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); - } - - if (TempDontRootMeBefore != tar->DontRootMeBefore()) { - tar->SetDontRootMeBefore(TempDontRootMeBefore); - } - } - break; - } - case SpellType_Buff: { - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_Buff); - - for(std::list::iterator itr = buffSpellList.begin(); itr != buffSpellList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (selectedBotSpell.SpellId == 0) { - continue; - } - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this) { - continue; - } - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[selectedBotSpell.SpellId].target_type == ST_Pet) && (tar != GetPet())) { - continue; - } - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[selectedBotSpell.SpellId].target_type == ST_Target || - spells[selectedBotSpell.SpellId].target_type == ST_Pet || - (tar == this && spells[selectedBotSpell.SpellId].target_type != ST_TargetsTarget) || - spells[selectedBotSpell.SpellId].target_type == ST_Group || - spells[selectedBotSpell.SpellId].target_type == ST_GroupTeleport || - (botClass == BARD && spells[selectedBotSpell.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && - tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate()) - || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { - if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { - continue; - } - } - - switch (tar->GetArchetype()) - { - case ARCHETYPE_CASTER: - //TODO: probably more caster specific spell effects in here - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS)) - { - continue; - } - break; - case ARCHETYPE_MELEE: - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana)) - { - continue; - } - break; - case ARCHETYPE_HYBRID: - //Hybrids get all buffs - default: - break; - } - - if (botClass == ENCHANTER && IsEffectInSpell(selectedBotSpell.SpellId, SE_Rune)) - { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case EQ::constants::stanceEfficient: - manaRatioToCast = 90.0f; - break; - case EQ::constants::stanceBalanced: - case EQ::constants::stanceAggressive: - manaRatioToCast = 75.0f; - break; - case EQ::constants::stanceReactive: - case EQ::constants::stanceBurn: - case EQ::constants::stanceBurnAE: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - break; - } - } - - if (CheckSpellRecastTimers(this, itr->SpellIndex)) - { - - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); - - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - - if (castedSpell) { - break; - } - } - } - break; - } - case SpellType_Escape: { - uint8 hpr = (uint8)GetHPRatio(); - bool mayGetAggro = false; - -#ifdef IPC - if (hpr <= 5 || (IsNPC() && CastToNPC()->IsInteractive() && tar != this) ) -#else - if (hpr > 15 && ((botClass == WIZARD) || (botClass == ENCHANTER) || (botClass == RANGER))) - mayGetAggro = HasOrMayGetAggro(); //classes have hate reducing spells - - if (hpr <= 15 || mayGetAggro) -#endif - { - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (botSpell.SpellId == 0) { - break; - } - if (IsInvulnerabilitySpell(botSpell.SpellId)) { - tar = this; //target self for invul type spells - } - - if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - break; - } - case SpellType_Nuke: { - if ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == PALADIN) || (botClass == SHADOWKNIGHT) || (botClass == WARRIOR))) - { - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - if (botClass == CLERIC || botClass == ENCHANTER) - { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case EQ::constants::stanceEfficient: - manaRatioToCast = 90.0f; - break; - case EQ::constants::stanceBalanced: - manaRatioToCast = 75.0f; - break; - case EQ::constants::stanceReactive: - case EQ::constants::stanceAggressive: - manaRatioToCast = 50.0f; - break; - case EQ::constants::stanceBurn: - case EQ::constants::stanceBurnAE: - manaRatioToCast = 25.0f; - break; - default: - manaRatioToCast = 50.0f; - break; - } - - //If we're at specified mana % or below, don't nuke as cleric or enchanter - if (GetManaRatio() <= manaRatioToCast) - break; - } - - if (botClass == MAGICIAN || botClass == SHADOWKNIGHT || botClass == NECROMANCER || botClass == PALADIN || botClass == RANGER || botClass == DRUID || botClass == CLERIC) { - if (tar->GetBodyType() == BT_Undead || tar->GetBodyType() == BT_SummonedUndead || tar->GetBodyType() == BT_Vampire) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead); - else if (tar->GetBodyType() == BT_Summoned || tar->GetBodyType() == BT_Summoned2 || tar->GetBodyType() == BT_Summoned3) - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); - } - - if (botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) { - if (botSpell.SpellId == 0) { - uint8 stunChance = (tar->IsCasting() ? 30: 15); - - if (botClass == PALADIN) - stunChance = 50; - - if (!tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { - botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); - } - } - } - - if (botClass == WIZARD && botSpell.SpellId == 0) { - botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar); - } - - if (botSpell.SpellId == 0) { - botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target); - } - if (botSpell.SpellId == 0) { - break; - } - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { - break; - } - if (IsFearSpell(botSpell.SpellId)) { - // don't let fear cast if the npc isn't snared or rooted - if (tar->GetSnaredAmount() == -1) { - if (!tar->IsRooted()) { - break; - } - } - } - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - break; - } - case SpellType_Dispel: { - if (tar->GetHPRatio() > 95.0f) { - if (!checked_los) { - if (!CheckLosFN(tar)) { - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - } - checked_los = true; - } - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (botSpell.SpellId == 0) { - break; - } - // TODO: Check target to see if there is anything to dispel - - if (tar->CountDispellableBuffs() > 0) { - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - } - break; - } - case SpellType_Pet: { - //keep mobs from recasting pets when they have them. - if (!IsPet() && !GetPetID() && !IsBotCharmer()) { - if (botClass == WIZARD) { - auto buffs_max = GetMaxBuffSlots(); - auto my_buffs = GetBuffs(); - int familiar_buff_slot = -1; - if (buffs_max && my_buffs) { - for (int index = 0; index < buffs_max; ++index) { - if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { - MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); - familiar_buff_slot = index; - break; - } - } - } - if (GetPetID()) - break; - - if (familiar_buff_slot >= 0) { - BuffFadeBySlot(familiar_buff_slot); - break; - } - - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); - } - else if (botClass == MAGICIAN) { - botSpell = GetBestBotMagicianPetSpell(this); - } - else { - botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); - } - - if (botSpell.SpellId == 0) - break; - - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - break; - } - case SpellType_InCombatBuff: { - // Allow all classes to use InCombatBuffs - if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { - std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); - - for (std::list::iterator itr = buffSpellList.begin(); itr != buffSpellList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (selectedBotSpell.SpellId == 0) { - continue; - } - // no buffs with illusions.. use #bot command to cast illusions - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this) { - continue; - } - //no teleport spells use #bot command to cast teleports - if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor)) { - continue; - } - // can not cast buffs for your own pet only on another pet that isn't yours - if ((spells[selectedBotSpell.SpellId].target_type == ST_Pet) && (tar != GetPet())) { - continue; - } - - //Conversion Spells - if (IsSelfConversionSpell(selectedBotSpell.SpellId)) { - if (GetManaRatio() > 90.0f || GetHPRatio() < 50.0f || GetHPRatio() < (GetManaRatio() + 10.0f)) { - break; //don't cast if low hp, lots of mana, or if mana is higher than hps - } - } - - // Validate target - // TODO: Add ST_TargetsTarget support for Buffing. - if ( - !( - ( - spells[selectedBotSpell.SpellId].target_type == ST_Target || - spells[selectedBotSpell.SpellId].target_type == ST_Pet || - (tar == this && spells[selectedBotSpell.SpellId].target_type != ST_TargetsTarget) || - spells[selectedBotSpell.SpellId].target_type == ST_Group || - spells[selectedBotSpell.SpellId].target_type == ST_GroupTeleport || - (botClass == BARD && spells[selectedBotSpell.SpellId].target_type == ST_AEBard) - ) && - !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && - tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0 - ) - ) { - continue; - } - - // Put the zone levitate and movement check here since bots are able to bypass the client casting check - if ( - (IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate()) || - (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor()) - ) { - if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { - continue; - } - } - - if (!IsGroupSpell(selectedBotSpell.SpellId)) { - //Only check archetype if spell is not a group spell - switch (tar->GetArchetype()) { - case ARCHETYPE_CASTER: - //TODO: probably more caster specific spell effects in here - if ( - ( - IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_DamageShield) - ) && - spells[selectedBotSpell.SpellId].target_type != ST_Self - ) { - continue; - } - break; - case ARCHETYPE_MELEE: - if ( - ( - IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) || - IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana) - ) && - spells[selectedBotSpell.SpellId].target_type != ST_Self - ) { - continue; - } - break; - case ARCHETYPE_HYBRID: - //Hybrids get all buffs - default: - break; - } - } - // TODO: Add TriggerSpell Support for Exchanter Runes - if (botClass == ENCHANTER && IsEffectInSpell(selectedBotSpell.SpellId, SE_Rune)) { - float manaRatioToCast = 75.0f; - - switch(GetBotStance()) { - case EQ::constants::stanceEfficient: - manaRatioToCast = 90.0f; - break; - case EQ::constants::stanceBalanced: - case EQ::constants::stanceAggressive: - manaRatioToCast = 75.0f; - break; - case EQ::constants::stanceReactive: - case EQ::constants::stanceBurn: - case EQ::constants::stanceBurnAE: - manaRatioToCast = 50.0f; - break; - default: - manaRatioToCast = 75.0f; - break; - } - - //If we're at specified mana % or below, don't rune as enchanter - if (GetManaRatio() <= manaRatioToCast) { - break; - } - } - - if (CheckSpellRecastTimers(this, itr->SpellIndex)) { - uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); - if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) - tar->SetDontBuffMeBefore(TempDontBuffMeBefore); - } - - if (castedSpell) { - break; - } - } - } - break; - } - case SpellType_Lifetap: { - if (GetHPRatio() < 90.0f) { - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (botSpell.SpellId == 0) - break; - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) - break; - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - break; - } - case SpellType_Snare: { - if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); - - if (botSpell.SpellId == 0) - break; - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) - break; - - uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); - } - - if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) - tar->SetDontSnareMeBefore(TempDontSnareMeBefore); - } - break; - } - case SpellType_DOT: { - if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - if (GetClass() == BARD) { - std::list dotList = GetPrioritizedBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (std::list::iterator itr = dotList.begin(); itr != dotList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (selectedBotSpell.SpellId == 0) - continue; - - if (CheckSpellRecastTimers(this, itr->SpellIndex)) - { - - if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0)) - continue; - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || castedSpell) - break; - } - } - else { - std::list dotList = GetBotSpellsBySpellType(this, SpellType_DOT); - - const int maxDotSelect = 5; - int dotSelectCounter = 0; - - for (std::list::iterator itr = dotList.begin(); itr != dotList.end(); ++itr) { - BotSpell selectedBotSpell = *itr; - - if (selectedBotSpell.SpellId == 0) - continue; - - if (CheckSpellRecastTimers(this, itr->SpellIndex)) - { - - if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0)) - continue; - - uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); - } - - if (TempDontDotMeBefore != tar->DontDotMeBefore()) - tar->SetDontDotMeBefore(TempDontDotMeBefore); - } - - dotSelectCounter++; - - if ((dotSelectCounter == maxDotSelect) || castedSpell) - break; - } - } - } - break; - } - case SpellType_Slow: { - if (tar->GetHPRatio() <= 99.0f) { - - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - switch (botClass) { - case BARD: { - // probably needs attackable check - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_Slow); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) - continue; - if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? - continue; - if (spells[iter.SpellId].target_type != ST_Target) - continue; - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - } - if (castedSpell) - break; - } - - break; - } - case ENCHANTER: { - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - case SHAMAN: - case BEASTLORD: { - botSpell = GetBestBotSpellForDiseaseBasedSlow(this); - - if (botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].resist_difficulty))) - botSpell = GetBestBotSpellForMagicBasedSlow(this); - break; - } - } - - if (botSpell.SpellId == 0) - break; - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) - break; - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - - if (castedSpell && GetClass() != BARD) { - BotGroupSay( - this, - fmt::format( - "Attempting to slow {} with {}.", - tar->GetCleanName(), - spells[botSpell.SpellId].name - ).c_str() - ); - } - } - break; - } - case SpellType_Debuff: { - if ((tar->GetHPRatio() <= 99.0f) && (tar->GetHPRatio() > 20.0f)) - { - if (!checked_los) { - if (!CheckLosFN(tar)) - break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call - - checked_los = true; - } - - botSpell = GetBestBotSpellForResistDebuff(this, tar); - - if (botSpell.SpellId == 0) - botSpell = GetDebuffBotSpell(this, tar); - - if (botSpell.SpellId == 0) - break; - - if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) - break; - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); - } - } - break; - } - case SpellType_Cure: { - if (GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2)) - { - botSpell = GetBestBotSpellForCure(this, tar); - - if (botSpell.SpellId == 0) - break; - - uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); - - castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); - - if (castedSpell) { - if (botClass != BARD) { - if (IsGroupSpell(botSpell.SpellId)) { - Group *g; - - if (HasGroup()) { - Group *g = GetGroup(); - - if (g) { - for ( int i = 0; imembers[i] && !g->members[i]->qglobal) { - if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) - g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } - } - else { - if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) { - tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); - } - } - } - } - } - break; - } + case SpellType_Mez: + return BotCastMez(tar, botLevel, checked_los, botSpell, raid); + case SpellType_Heal: + return BotCastHeal(tar, botLevel, botClass, botSpell, raid); + case SpellType_Root: + return BotCastRoot(tar, botLevel, iSpellTypes, botSpell, checked_los); + case SpellType_Buff: + return BotCastBuff(tar, botLevel, botClass); + case SpellType_Escape: + return BotCastEscape(tar, botClass, botSpell, iSpellTypes); + case SpellType_Nuke: + return BotCastNuke(tar, botLevel, botClass, botSpell, checked_los); + case SpellType_Dispel: + return BotCastDispel(tar, botSpell, iSpellTypes, checked_los); + case SpellType_Pet: + return BotCastPet(tar, botClass, botSpell); + case SpellType_InCombatBuff: + return BotCastCombatBuff(tar, botLevel, botClass); + case SpellType_Lifetap: + return BotCastLifetap(tar, botLevel, botSpell, checked_los, iSpellTypes); + case SpellType_Snare: + return BotCastSnare(tar, botLevel, botSpell, checked_los, iSpellTypes); + case SpellType_DOT: + return BotCastDOT(tar, botLevel, botSpell, checked_los); + case SpellType_Slow: + return BotCastSlow(tar, botLevel, botClass, botSpell, checked_los, raid); + case SpellType_Debuff: + return BotCastDebuff(tar, botLevel, botSpell, checked_los); + case SpellType_Cure: + return BotCastCure(tar, botClass, botSpell, raid); case SpellType_Resurrect: - break; - case SpellType_HateRedux: { - // assumed group member at this point - if (GetClass() == BARD) { - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_HateRedux); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) - continue; - if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? - continue; - if (spells[iter.SpellId].target_type != ST_Target) - continue; - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - if (IsValidSpellRange(botSpell.SpellId, tar)) { - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - } - if (castedSpell) { - BotGroupSay( - this, - fmt::format( - "Attempting to reduce hate on {} with {}.", - tar->GetCleanName(), - spells[iter.SpellId].name - ).c_str() - ); - break; - } - } - } - - break; - } - case SpellType_InCombatBuffSong: { - if (tar != this) { // In-Combat songs can be cast Out-of-Combat in preparation for battle - break; - } - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_InCombatBuffSong); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) - continue; - if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: - continue; - } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (castedSpell) - break; - } - - break; - } - case SpellType_OutOfCombatBuffSong: { - if (GetClass() != BARD || tar != this || IsEngaged()) // Out-of-Combat songs can not be cast in combat - break; - - std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_OutOfCombatBuffSong); - for (auto iter : botSongList) { - if (!iter.SpellId) - continue; - if (!CheckSpellRecastTimers(this, iter.SpellIndex)) - continue; - if (spells[iter.SpellId].zone_type != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zone_type != zone->GetZoneType()) // is this bit or index? - continue; - switch (spells[iter.SpellId].target_type) { - case ST_AEBard: - case ST_AECaster: - case ST_GroupTeleport: - case ST_Group: - case ST_Self: - break; - default: - continue; - } - if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) - continue; - - castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); - if (castedSpell) - break; - } - - break; - } - case SpellType_PreCombatBuff: { - break; - } - case SpellType_PreCombatBuffSong: { - break; - } + return false; + case SpellType_HateRedux: + return BotCastHateReduction(tar, botLevel, botSpell); + case SpellType_InCombatBuffSong: + return BotCastCombatSong(tar, botLevel); + case SpellType_OutOfCombatBuffSong: + return BotCastSong(tar, botLevel); + case SpellType_PreCombatBuff: + return false; + case SpellType_PreCombatBuffSong: + return false; default: + return false; + } + + return false; +} + +bool Bot::BotCastSong(Mob* tar, uint8 botLevel) { + bool casted_spell = false; + if (GetClass() != BARD || tar != this || IsEngaged()) // Out-of-Combat songs can not be cast in combat + return casted_spell; + + for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_OutOfCombatBuffSong); + auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zone_type != zone->GetZoneType()) + continue; + switch (spells[iter.SpellId].target_type) { + case ST_AEBard: + case ST_AECaster: + case ST_GroupTeleport: + case ST_Group: + case ST_Self: + break; + default: + continue; + } + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (casted_spell) break; } - return castedSpell; + return casted_spell; } -bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { +bool Bot::BotCastCombatSong(Mob* tar, uint8 botLevel) { + bool casted_spell = false; + if (tar != this) { // In-Combat songs can be cast Out-of-Combat in preparation for battle + return casted_spell; + } + for (auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_InCombatBuffSong); + auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zone_type != zone->GetZoneType()) + continue; + switch (spells[iter.SpellId].target_type) { + case ST_AEBard: + case ST_AECaster: + case ST_GroupTeleport: + case ST_Group: + case ST_Self: + break; + default: + continue; + } + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (casted_spell) + break; + } + + return casted_spell; +} + +bool Bot::BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpell) { + bool casted_spell = false; + if (GetClass() == BARD) { + std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_HateRedux); + for (auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zone_type != zone->GetZoneType()) + continue; + if (spells[iter.SpellId].target_type != ST_Target) + continue; + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + } + if (casted_spell) { + BotGroupSay( + this, + fmt::format( + "Attempting to reduce hate on {} with {}.", + tar->GetCleanName(), + spells[iter.SpellId].name + ).c_str() + ); + } + } + } + + return casted_spell; +} + +bool Bot::BotCastCure(Mob* tar, uint8 botClass, BotSpell& botSpell, Raid* raid) { + bool casted_spell = false; + if ( + GetNeedsCured(tar) && + (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && + GetNumberNeedingHealedInGroup(25, false, raid) <= 0 && + GetNumberNeedingHealedInGroup(40, false, raid) <= 2 + ) { + botSpell = GetBestBotSpellForCure(this, tar); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); + + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime); + + if (casted_spell && botClass != BARD) { + if (IsGroupSpell(botSpell.SpellId)) { + if (HasGroup()) { + Group const* group = GetGroup(); + if (group) { + for (auto member : group->members) { + if ( + member && + !member->qglobal && + TempDontCureMeBeforeTime != tar->DontCureMeBefore() + ) { + member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } else if (IsRaidGrouped()) { + uint32 r_group = raid->GetGroup(GetName()); + if (r_group) { + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + for (auto iter: raid_group_members) { + if ( + iter.member && + !iter.member->qglobal && + TempDontCureMeBeforeTime != tar->DontCureMeBefore() + ) { + iter.member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + } else if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) { + tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + return casted_spell; +} + +bool Bot::BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool checked_los) { + bool casted_spell = false; + if ((tar->GetHPRatio() <= 99.0f) && (tar->GetHPRatio() > 20.0f)) + { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + botSpell = GetBestBotSpellForResistDebuff(this, tar); + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetDebuffBotSpell(this, tar); + } + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + if (! + ( + !tar->IsImmuneToSpell(botSpell.SpellId, this) && + (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0) + ) + ) { + return casted_spell; + } + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + return casted_spell; +} + +bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los, Raid* raid) { + bool casted_spell = false; + if (tar->GetHPRatio() <= 99.0f) { + + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + switch (botClass) { + case BARD: { + // probably needs attackable check + for ( + auto botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_Slow); + auto iter : botSongList + ) { + + if (!iter.SpellId) { + continue; + } + + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) { + continue; + } + + if (spells[iter.SpellId].zone_type != zone->GetZoneType()) { + continue; + } + + if (spells[iter.SpellId].target_type != ST_Target) { + continue; + } + + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) { + continue; + } + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + } + + if (casted_spell) { + return casted_spell; + } + } + + break; + } + case ENCHANTER: { + botSpell = GetBestBotSpellForMagicBasedSlow(this); + break; + } + case SHAMAN: + case BEASTLORD: { + botSpell = GetBestBotSpellForDiseaseBasedSlow(this); + + if (botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].resist_difficulty))) + botSpell = GetBestBotSpellForMagicBasedSlow(this); + break; + } + } + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { + return casted_spell; + } + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + + if (casted_spell && GetClass() != BARD) { + if (raid) { + const auto msg = fmt::format("Attempting to slow {}.", tar->GetCleanName()); + raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100); + } else { + BotGroupSay( + this, + fmt::format( + "Attempting to slow {} with {}.", + tar->GetCleanName(), + spells[botSpell.SpellId].name + ).c_str() + ); + } + } + } + return casted_spell; +} + +bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const bool& checked_los) { + bool casted_spell = false; + + if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + if (GetClass() == BARD) { + std::list dotList = GetPrioritizedBotSpellsBySpellType(this, SpellType_DOT); + + const int maxDotSelect = 5; + int dotSelectCounter = 0; + + for (const auto& s : dotList) { + + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (CheckSpellRecastTimers(this, s.SpellIndex)) + { + + if (!(!tar->IsImmuneToSpell(s.SpellId, this) && tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { + continue; + } + + uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); + + casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); + + if (TempDontDotMeBefore != tar->DontDotMeBefore()) { + tar->SetDontDotMeBefore(TempDontDotMeBefore); + } + } + + dotSelectCounter++; + + if ((dotSelectCounter == maxDotSelect) || casted_spell) { + break; + } + } + } + else { + std::list dotList = GetBotSpellsBySpellType(this, SpellType_DOT); + + const int maxDotSelect = 5; + int dotSelectCounter = 0; + + for (const auto& s : dotList) { + + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (CheckSpellRecastTimers(this, s.SpellIndex)) { + + if (!(!tar->IsImmuneToSpell(s.SpellId, this) && + tar->CanBuffStack(s.SpellId, botLevel, true) >= 0)) { + continue; + } + + uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore); + } + + if (TempDontDotMeBefore != tar->DontDotMeBefore()) { + tar->SetDontDotMeBefore(TempDontDotMeBefore); + } + } + + dotSelectCounter++; + + if ((dotSelectCounter == maxDotSelect) || casted_spell) { + return casted_spell; + } + } + } + } + return casted_spell; +} + +bool Bot::BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { + bool casted_spell = false; + if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { + return casted_spell; + } + + uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); + } + + if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) { + tar->SetDontSnareMeBefore(TempDontSnareMeBefore); + } + } + return casted_spell; +} + +bool Bot::BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { + bool casted_spell = false; + if (GetHPRatio() < 90.0f) { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { + return casted_spell; + } + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + return casted_spell; +} + +bool Bot::BotCastCombatBuff(Mob* tar, uint8 botLevel, uint8 botClass) { + + bool casted_spell = false; + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for (const auto& s : buffSpellList) { + + if (!IsValidSpell(s.SpellId)) { + continue; + } + // no buffs with illusions.. use #bot command to cast illusions + if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { + continue; + } + //no teleport spells use #bot command to cast teleports + if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { + continue; + } + // can not cast buffs for your own pet only on another pet that isn't yours + if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { + continue; + } + + //Conversion Spells + if ( + IsSelfConversionSpell(s.SpellId) && + ( + GetManaRatio() > 90.0f || + GetHPRatio() < 50.0f || + GetHPRatio() < (GetManaRatio() + 10.0f) + ) + ) { + break; //don't cast if low hp, lots of mana, or if mana is higher than hps + } + + // Validate target + // TODO: Add ST_TargetsTarget support for Buffing. + if ( + !( + ( + spells[s.SpellId].target_type == ST_Target || + spells[s.SpellId].target_type == ST_Pet || + (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || + spells[s.SpellId].target_type == ST_Group || + spells[s.SpellId].target_type == ST_GroupTeleport || + (botClass == BARD && spells[s.SpellId].target_type == ST_AEBard) + ) && + !tar->IsImmuneToSpell(s.SpellId, this) && + tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 + ) + ) { + continue; + } + + // Put the zone levitate and movement check here since bots are able to bypass the client casting check + if ( + ((IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || + (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) && + (botClass != BARD || !IsSpellUsableThisZoneType(s.SpellId, zone->GetZoneType())) + ) { + continue; + } + + if (!IsGroupSpell(s.SpellId)) { + //Only check archetype if spell is not a group spell + //Hybrids get all buffs + switch (tar->GetArchetype()) { + case ARCHETYPE_CASTER: + //TODO: probably more caster specific spell effects in here + if ( + ( + IsEffectInSpell(s.SpellId, SE_AttackSpeed) || + IsEffectInSpell(s.SpellId, SE_ATK) || + IsEffectInSpell(s.SpellId, SE_STR) || + IsEffectInSpell(s.SpellId, SE_ReverseDS) || + IsEffectInSpell(s.SpellId, SE_DamageShield) + ) && + spells[s.SpellId].target_type != ST_Self + ) { + continue; + } + break; + case ARCHETYPE_MELEE: + if ( + ( + IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || + IsEffectInSpell(s.SpellId, SE_ManaPool) || + IsEffectInSpell(s.SpellId, SE_CastingLevel) || + IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || + IsEffectInSpell(s.SpellId, SE_CurrentMana) + ) && + spells[s.SpellId].target_type != ST_Self + ) { + continue; + } + break; + default: + break; + } + } + // TODO: Add TriggerSpell Support for Exchanter Runes + if (botClass == ENCHANTER && IsEffectInSpell(s.SpellId, SE_Rune)) { + float manaRatioToCast = 75.0f; + + switch(GetBotStance()) { + case EQ::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQ::constants::stanceBalanced: + case EQ::constants::stanceAggressive: + manaRatioToCast = 75.0f; + break; + case EQ::constants::stanceReactive: + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + manaRatioToCast = 50.0f; + break; + default: + manaRatioToCast = 75.0f; + break; + } + + //If we're at specified mana % or below, don't rune as enchanter + if (GetManaRatio() <= manaRatioToCast) { + break; + } + } + + if (CheckSpellRecastTimers(this, s.SpellIndex)) { + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); + if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { + tar->SetDontBuffMeBefore(TempDontBuffMeBefore); + } + } + + if (casted_spell) { + return casted_spell; + } + } + } + return casted_spell; +} + +bool Bot::BotCastPet(Mob* tar, uint8 botClass, BotSpell& botSpell) { + bool casted_spell = false; + if (!IsPet() && !GetPetID() && !IsBotCharmer()) { + if (botClass == WIZARD) { + auto buffs_max = GetMaxBuffSlots(); + auto my_buffs = GetBuffs(); + int familiar_buff_slot = -1; + if (buffs_max && my_buffs) { + for (int index = 0; index < buffs_max; ++index) { + if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { + MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); + familiar_buff_slot = index; + break; + } + } + } + if (GetPetID()) { + return casted_spell; + } + if (familiar_buff_slot >= 0) { + BuffFadeBySlot(familiar_buff_slot); + return casted_spell; + } + + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); + } + else if (botClass == MAGICIAN) { + botSpell = GetBestBotMagicianPetSpell(this); + } + else { + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); + } + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + return casted_spell; +} + +bool Bot::BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool& checked_los) { + + bool casted_spell = false; + if (tar->GetHPRatio() > 95.0f) { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + // TODO: Check target to see if there is anything to dispel + + if (tar->CountDispellableBuffs() > 0 && IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + return casted_spell; +} + +bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, const bool& checked_los) { + + bool casted_spell = false; + if ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == PALADIN) || (botClass == SHADOWKNIGHT) || (botClass == WARRIOR))) + { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + if (botClass == CLERIC || botClass == ENCHANTER) + { + float manaRatioToCast = 75.0f; + + switch(GetBotStance()) { + case EQ::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQ::constants::stanceBalanced: + manaRatioToCast = 75.0f; + break; + case EQ::constants::stanceReactive: + case EQ::constants::stanceAggressive: + manaRatioToCast = 50.0f; + break; + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + manaRatioToCast = 25.0f; + break; + default: + manaRatioToCast = 50.0f; + break; + } + + //If we're at specified mana % or below, don't nuke as cleric or enchanter + if (GetManaRatio() <= manaRatioToCast) + return casted_spell; + } + + if (botClass == MAGICIAN || botClass == SHADOWKNIGHT || botClass == NECROMANCER || botClass == PALADIN || botClass == RANGER || botClass == DRUID || botClass == CLERIC) { + if (tar->GetBodyType() == BT_Undead || tar->GetBodyType() == BT_SummonedUndead || tar->GetBodyType() == BT_Vampire) + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead); + else if (tar->GetBodyType() == BT_Summoned || tar->GetBodyType() == BT_Summoned2 || tar->GetBodyType() == BT_Summoned3) + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); + } + + if ((botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) && !IsValidSpell(botSpell.SpellId)) { + uint8 stunChance = (tar->IsCasting() ? 30: 15); + + if (botClass == PALADIN) { + stunChance = 50; + } + + if (!tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { + botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); + } + } + + if (botClass == WIZARD && botSpell.SpellId == 0) { + botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar); + } + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target); + } + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) { + return casted_spell; + } + if (IsFearSpell(botSpell.SpellId) && tar->GetSnaredAmount() == -1 && !tar->IsRooted()) { + return casted_spell; + } + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + return casted_spell; +} + +bool Bot::BotCastEscape(Mob*& tar, uint8 botClass, BotSpell& botSpell, uint32 iSpellTypes) { + + bool casted_spell = false; + auto hpr = (uint8) GetHPRatio(); + bool mayGetAggro = false; + + if (hpr > 15 && ((botClass == WIZARD) || (botClass == ENCHANTER) || (botClass == RANGER))) { + mayGetAggro = HasOrMayGetAggro(); + } + + if (hpr <= 15 || mayGetAggro) + { + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + + if (IsInvulnerabilitySpell(botSpell.SpellId)) { + tar = this; //target self for invul type spells + } + + if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + return casted_spell; +} + +bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) { + bool casted_spell = false; + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_Buff); + + for(const auto& s : buffSpellList) { + + if (!IsValidSpell(s.SpellId)) { + continue; + } + + // no buffs with illusions.. use #bot command to cast illusions + if (IsEffectInSpell(s.SpellId, SE_Illusion) && tar != this) { + continue; + } + + //no teleport spells use #bot command to cast teleports + if (IsEffectInSpell(s.SpellId, SE_Teleport) || IsEffectInSpell(s.SpellId, SE_Succor)) { + continue; + } + // can not cast buffs for your own pet only on another pet that isn't yours + if ((spells[s.SpellId].target_type == ST_Pet) && (tar != GetPet())) { + continue; + } + + // Validate target + // TODO: Add ST_TargetsTarget support for Buffing. + if ( + !( + ( + spells[s.SpellId].target_type == ST_Target || + spells[s.SpellId].target_type == ST_Pet || + (tar == this && spells[s.SpellId].target_type != ST_TargetsTarget) || + spells[s.SpellId].target_type == ST_Group || + spells[s.SpellId].target_type == ST_GroupTeleport || + (botClass == BARD && spells[s.SpellId].target_type == ST_AEBard) + ) && + !tar->IsImmuneToSpell(s.SpellId, this) && + tar->CanBuffStack(s.SpellId, botLevel, true) >= 0 + ) + ) { + continue; + } + + // Put the zone levitate and movement check here since bots are able to bypass the client casting check + if ( + ( + (IsEffectInSpell(s.SpellId, SE_Levitate) && !zone->CanLevitate()) || + (IsEffectInSpell(s.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor()) + ) && + (botClass != BARD || !IsSpellUsableThisZoneType(s.SpellId, zone->GetZoneType())) + ) { + continue; + } + + switch (tar->GetArchetype()) + { + case ARCHETYPE_CASTER: + //TODO: probably more caster specific spell effects in here + if (IsEffectInSpell(s.SpellId, SE_AttackSpeed) || IsEffectInSpell(s.SpellId, SE_ATK) || + IsEffectInSpell(s.SpellId, SE_STR) || IsEffectInSpell(s.SpellId, SE_ReverseDS)) + { + continue; + } + break; + case ARCHETYPE_MELEE: + if (IsEffectInSpell(s.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(s.SpellId, SE_ManaPool) || + IsEffectInSpell(s.SpellId, SE_CastingLevel) || IsEffectInSpell(s.SpellId, SE_ManaRegen_v2) || + IsEffectInSpell(s.SpellId, SE_CurrentMana)) + { + continue; + } + break; + default: //Hybrids get all buffs + break; + } + + if (botClass == ENCHANTER && IsEffectInSpell(s.SpellId, SE_Rune)) + { + float manaRatioToCast = 75.0f; + + switch (GetBotStance()) { + case EQ::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQ::constants::stanceBalanced: + case EQ::constants::stanceAggressive: + manaRatioToCast = 75.0f; + break; + case EQ::constants::stanceReactive: + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + manaRatioToCast = 50.0f; + break; + default: + manaRatioToCast = 75.0f; + break; + } + + //If we're at specified mana % or below, don't rune as enchanter + if (GetManaRatio() <= manaRatioToCast) { + return casted_spell; + } + } + + if (CheckSpellRecastTimers(this, s.SpellIndex)) + { + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + + casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontBuffMeBefore); + + if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) { + tar->SetDontBuffMeBefore(TempDontBuffMeBefore); + } + } + } + } + return casted_spell; +} + +bool Bot::BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los) { + bool casted_spell = false; + if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { + if (!checked_los && !CheckLosFN(tar)) { + return casted_spell; + } + + // TODO: If there is a ranger in the group then don't allow root spells + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (!IsValidSpell(botSpell.SpellId)) { + return casted_spell; + } + if (tar->CanBuffStack(botSpell.SpellId, botLevel, true) == 0) { + return casted_spell; + } + uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); + + if (IsValidSpellRange(botSpell.SpellId, tar)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); + } + + if (TempDontRootMeBefore != tar->DontRootMeBefore()) { + tar->SetDontRootMeBefore(TempDontRootMeBefore); + } + } + return casted_spell; +} + +bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpell, Raid* raid) { + bool casted_spell = false; + if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { + auto hpr = (uint8)tar->GetHPRatio(); + bool hasAggro = false; + bool isPrimaryHealer = false; + + if (HasGroup() || IsRaidGrouped()) { + isPrimaryHealer = IsGroupHealer(); + } + + if (hpr < 95 || (tar->IsClient() && (hpr < 95)) || (botClass == BARD)) { + if (tar->GetClass() == NECROMANCER && hpr >= 40) { + return false; + } + + if (tar->GetClass() == SHAMAN && hpr >= 80) { + return false; + } + + // Evaluate the situation + if ((IsEngaged()) && ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN))) { + if (tar->GetTarget() && tar->GetTarget()->GetHateTop() && tar->GetTarget()->GetHateTop() == tar) { + hasAggro = true; + } + + if (hpr < 35) { + botSpell = GetBestBotSpellForFastHeal(this); + } + else if (hpr >= 35 && hpr < 70) { + if (GetNumberNeedingHealedInGroup(60, false, raid) >= 3) { + botSpell = GetBestBotSpellForGroupHeal(this); + } + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else if (hpr >= 70 && hpr < 95) { + if (GetNumberNeedingHealedInGroup(80, false, raid) >= 3) { + botSpell = GetBestBotSpellForGroupHealOverTime(this); + } + + if (hasAggro) { + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else { + if (!tar->FindType(SE_HealOverTime)) { + botSpell = GetBestBotSpellForHealOverTime(this); + } + } + } + else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) { + if (GetNumberNeedingHealedInGroup(40, true, raid) >= 2) { + botSpell = GetBestBotSpellForGroupCompleteHeal(this); + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForGroupHeal(this); + } + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForGroupHealOverTime(this); + } + + if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else if (GetNumberNeedingHealedInGroup(60, true, raid) >= 2) { + botSpell = GetBestBotSpellForGroupHeal(this); + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForGroupHealOverTime(this); + } + + if (hpr < 40 && !IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else if (hpr < 40) { + botSpell = GetBestBotSpellForPercentageHeal(this); + } + else if (hpr >= 40 && hpr < 75) { + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + } + else { + if (hpr < 90 && !tar->FindType(SE_HealOverTime)) { + botSpell = GetBestBotSpellForHealOverTime(this); + } + } + } + else { + float hpRatioToCast = 0.0f; + + switch (GetBotStance()) { + case EQ::constants::stanceEfficient: + case EQ::constants::stanceAggressive: + hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f; + break; + case EQ::constants::stanceBalanced: + hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f; + break; + case EQ::constants::stanceReactive: + hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f; + break; + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f; + break; + default: + hpRatioToCast = isPrimaryHealer ? 100.0f : 0.0f; + break; + } + + //If we're at specified mana % or below, don't heal as hybrid + if (tar->GetHPRatio() <= hpRatioToCast) { + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + } + } + + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + } + if (!IsValidSpell(botSpell.SpellId)) { + botSpell = GetFirstBotSpellForSingleTargetHeal(this); + } + if (botSpell.SpellId == 0 && botClass == BARD) { + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); + } + + if (!IsValidSpell(botSpell.SpellId)) { + return false; + } + // Can we cast this spell on this target? + if (!(spells[botSpell.SpellId].target_type==ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) + && tar->CanBuffStack(botSpell.SpellId, botLevel, true) < 0) { + return false; + } + + uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); + + if (IsValidSpellRange(botSpell.SpellId, tar) || botClass == BARD) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + } + + if (casted_spell && botClass != BARD) { + if (IsGroupSpell(botSpell.SpellId)) { + if (HasGroup()) { + Group *group = GetGroup(); + if (group) { + BotGroupSay( + this, + fmt::format( + "Casting {}.", + spells[botSpell.SpellId].name + ).c_str() + ); + + for (const auto& member : group->members) { + if (member && !member->qglobal) { + member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); + } + } + } else if (IsRaidGrouped()) { + uint32 r_group = raid->GetGroup(GetName()); + const auto msg = fmt::format("Casting {}.", spells[botSpell.SpellId].name); + raid->RaidGroupSay(msg.c_str(), GetCleanName(), 0, 100); + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + for (const auto& rgm : raid_group_members) { + if (rgm.member && !rgm.member->qglobal) { + rgm.member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); + } + } + } + } else { + if (tar != this) { //we don't need spam of bots healing themselves + BotGroupSay( + this, + fmt::format( + "Casting {} on {}.", + spells[botSpell.SpellId].name, + tar->GetCleanName() + ).c_str() + ); + } + } + } + tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000); + } + } + } + return casted_spell; +} + +bool Bot::BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid) { + bool casted_spell = false; + if (!checked_los && !CheckLosFN(tar)) { + return false; + } + + //TODO + //Check if single target or AoE mez is best + //if (TARGETS ON MT IS => 3 THEN botSpell = AoEMez) + //if (TARGETS ON MT IS <= 2 THEN botSpell = BestMez) + + botSpell = GetBestBotSpellForMez(this); + + if (!IsValidSpell(botSpell.SpellId)) { + return false; + } + + Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); + + if (!addMob) { + return false; + } + + if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) { + return false; + } + + if (IsValidSpellRange(botSpell.SpellId, addMob)) { + casted_spell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); + } + if (casted_spell) { + if (raid) { + raid->RaidSay( + GetCleanName(), + fmt::format( + "Attempting to mesmerize {} with {}.", + addMob->GetCleanName(), + spells[botSpell.SpellId].name + ).c_str(), + 0, + 100 + ); + } else { + BotGroupSay( + this, + fmt::format( + "Attempting to mesmerize {} with {}.", + addMob->GetCleanName(), + spells[botSpell.SpellId].name + ).c_str() + ); + } + } + return casted_spell; +} + +bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { bool result = false; // manacost has special values, -1 is no mana cost, -2 is instant cast (no mana) int32 manaCost = mana_cost; - if (manaCost == -1) + if (manaCost == -1) { manaCost = spells[AIBot_spells[i].spellid].mana; - else if (manaCost == -2) + } else if (manaCost == -2) { manaCost = 0; + } - int32 extraMana = 0; - int32 hasMana = GetMana(); + int64 hasMana = GetMana(); // Allow bots to cast buff spells even if they are out of mana - if (RuleB(Bots, FinishBuffing)) { - if (manaCost > hasMana) { - // Let's have the bots complete the buff time process - if (AIBot_spells[i].type & SpellType_Buff) { - extraMana = manaCost - hasMana; - SetMana(manaCost); - } - } + if ( + RuleB(Bots, FinishBuffing) && + manaCost > hasMana && AIBot_spells[i].type & SpellType_Buff + ) { + SetMana(manaCost); } float dist2 = 0; @@ -1224,7 +1294,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain ) && ( mana_cost <= GetMana() || - GetMana() == GetMaxMana() + IsBotNonSpellFighter() ) ) { casting_spell_AIindex = i; @@ -1238,19 +1308,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain // if the spell wasn't casted, then take back any extra mana that was given to the bot to cast that spell if (!result) { SetMana(hasMana); - extraMana = false; } - else { //handle spell recast and recast timers - //if (GetClass() == BARD && IsGroupSpell(AIBot_spells[i].spellid)) { - // // Bard buff songs have been moved to their own npc spell type.. - // // Buff stacking is now checked as opposed to manipulating the timer to avoid rapid casting - - // //AIBot_spells[i].time_cancast = (spells[AIBot_spells[i].spellid].recast_time > (spells[AIBot_spells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].buffduration * 6000; - // //spellend_timer.Start(spells[AIBot_spells[i].spellid].cast_time); - //} - //else - // AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time; - + else { AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time; if (spells[AIBot_spells[i].spellid].timer_id > 0) { @@ -1271,21 +1330,14 @@ bool Bot::AI_PursueCastCheck() { LogAIDetail("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells"); if (!AICastSpell(GetTarget(), 100, SpellType_Snare)) { - if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap)) { - if (!AICastSpell(GetTarget(), 100, SpellType_Nuke)) { - /*AIautocastspell_timer->Start(RandomTimer(500, 2000), false); - result = true;*/ - result = true; - } - - result = true; + if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap) && !AICastSpell(GetTarget(), 100, SpellType_Nuke)) { } - result = true; } - if (!AIautocastspell_timer->Enabled()) + if (!AIautocastspell_timer->Enabled()) { AIautocastspell_timer->Start(RandomTimer(100, 250), false); + } } return result; @@ -1466,7 +1518,6 @@ bool Bot::AI_EngagedCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. uint8 botClass = GetClass(); - EQ::constants::StanceType botStance = GetBotStance(); bool mayGetAggro = HasOrMayGetAggro(); LogAIDetail("Engaged autocast check triggered (BOTS). Trying to cast healing spells then maybe offensive spells"); @@ -1515,7 +1566,6 @@ bool Bot::AI_EngagedCastCheck() { if (!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_DOT), SpellType_DOT)) { if (!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) { if (!AICastSpell(GetPet(), GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - //AIautocastspell_timer->Start(RandomTimer(100, 250), false); // Do not give healer classes a lot of time off or your tank's die failedToCast = true; } } @@ -1777,19 +1827,19 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { if (useFastHeals) { botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); - if (botSpell.SpellId == 0) + if (!IsValidSpell(botSpell.SpellId)) botSpell = GetBestBotSpellForFastHeal(this); } else { botSpell = GetBestBotSpellForPercentageHeal(this); - if (botSpell.SpellId == 0) { + if (!IsValidSpell(botSpell.SpellId)) { botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); } - if (botSpell.SpellId == 0) { + if (!IsValidSpell(botSpell.SpellId)) { botSpell = GetFirstBotSpellForSingleTargetHeal(this); } - if (botSpell.SpellId == 0) { + if (!IsValidSpell(botSpell.SpellId)) { botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); } } @@ -1800,13 +1850,21 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { LogAIDetail("target [{}] current_time [{}] donthealmebefore [{}]", tar->GetCleanName(), Timer::GetCurrentTime(), tar->DontHealMeBefore()); // If there is still no spell id, then there isn't going to be one so we are done - if (botSpell.SpellId == 0) + if (!IsValidSpell(botSpell.SpellId)) { return false; + } // Can we cast this spell on this target? - if (!(spells[botSpell.SpellId].target_type == ST_GroupTeleport || spells[botSpell.SpellId].target_type == ST_Target || tar == this) - && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + if (! + ( + spells[botSpell.SpellId].target_type == ST_GroupTeleport || + spells[botSpell.SpellId].target_type == ST_Target || + tar == this + ) && + tar->CanBuffStack(botSpell.SpellId, botLevel, true) < 0 + ) { return false; + } uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); if (IsValidSpellRange(botSpell.SpellId, tar)) { @@ -1830,8 +1888,7 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect) { std::list result; - auto bot_owner = botCaster->GetBotOwner(); - if (!bot_owner) { + if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { return result; } @@ -1862,8 +1919,7 @@ std::list Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType) { std::list result; - auto bot_owner = botCaster->GetBotOwner(); - if (!bot_owner) { + if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { return result; } @@ -1877,15 +1933,18 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, continue; } - if (IsEffectInSpell(botSpellList[i].spellid, spellEffect) || GetTriggerSpellID(botSpellList[i].spellid, spellEffect)) { - if (spells[botSpellList[i].spellid].target_type == targetType) { - BotSpell botSpell; - botSpell.SpellId = botSpellList[i].spellid; - botSpell.SpellIndex = i; - botSpell.ManaCost = botSpellList[i].manacost; - - result.push_back(botSpell); - } + if ( + ( + IsEffectInSpell(botSpellList[i].spellid, spellEffect) || + GetTriggerSpellID(botSpellList[i].spellid, spellEffect) + ) && + spells[botSpellList[i].spellid].target_type == targetType + ) { + BotSpell botSpell; + botSpell.SpellId = botSpellList[i].spellid; + botSpell.SpellIndex = i; + botSpell.ManaCost = botSpellList[i].manacost; + result.push_back(botSpell); } } } @@ -1896,8 +1955,7 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, std::list Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellType) { std::list result; - auto bot_owner = botCaster->GetBotOwner(); - if (!bot_owner) { + if (auto bot_owner = botCaster->GetBotOwner(); !bot_owner) { return result; } @@ -1951,7 +2009,7 @@ std::list Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa if (result.size() > 1) { result.sort( - [](BotSpell_wPriority& l, BotSpell_wPriority& r) { + [](BotSpell_wPriority const& l, BotSpell_wPriority const& r) { return l.Priority < r.Priority; } ); @@ -2001,12 +2059,12 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot *botCaster) { if (botCaster) { std::list botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP); - for (std::list::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); ++botSpellListItr) { + for (auto botSpellListItr : botSpellList) { // Assuming all the spells have been loaded into this list by level and in descending order - if (IsFastHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + if (IsFastHealSpell(botSpellListItr.SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr.SpellIndex)) { + result.SpellId = botSpellListItr.SpellId; + result.SpellIndex = botSpellListItr.SpellIndex; + result.ManaCost = botSpellListItr.ManaCost; break; } @@ -2027,9 +2085,9 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { std::list botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime); std::vector botSpellList = botCaster->AIBot_spells; - for (std::list::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) { + for (auto botSpellListItr : botHoTSpellList) { // 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--) { if (!IsValidSpell(botSpellList[i].spellid)) { // this is both to quit early to save cpu and to avoid casting bad spells @@ -2038,13 +2096,13 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) { } if ( - botSpellList[i].spellid == botSpellListItr->SpellId && + botSpellList[i].spellid == botSpellListItr.SpellId && (botSpellList[i].type & SpellType_Heal) && - CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex) + CheckSpellRecastTimers(botCaster, botSpellListItr.SpellIndex) ) { - result.SpellId = botSpellListItr->SpellId; - result.SpellIndex = botSpellListItr->SpellIndex; - result.ManaCost = botSpellListItr->ManaCost; + result.SpellId = botSpellListItr.SpellId; + result.SpellIndex = botSpellListItr.SpellIndex; + result.ManaCost = botSpellListItr.ManaCost; } } @@ -2644,11 +2702,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) { { level_mod = -level_mod; } - bool needsMagicResistDebuff = (tar->GetMR() + level_mod) > 100 ? true: false; - bool needsColdResistDebuff = (tar->GetCR() + level_mod) > 100 ? true: false; - bool needsFireResistDebuff = (tar->GetFR() + level_mod) > 100 ? true: false; - bool needsPoisonResistDebuff = (tar->GetPR() + level_mod) > 100 ? true: false; - bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100 ? true: false; + bool needsMagicResistDebuff = (tar->GetMR() + level_mod) > 100; + bool needsColdResistDebuff = (tar->GetCR() + level_mod) > 100; + bool needsFireResistDebuff = (tar->GetFR() + level_mod) > 100; + bool needsPoisonResistDebuff = (tar->GetPR() + level_mod) > 100; + bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100; if (botCaster && botCaster->AI_HasSpells()) { std::vector botSpellList = botCaster->AIBot_spells; @@ -3515,4 +3573,4 @@ bool Bot::IsValidSpellRange(uint16 spell_id, Mob const* tar) { } } return false; -} \ No newline at end of file +} diff --git a/zone/client.cpp b/zone/client.cpp index 36d89a202..7815e22c4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -807,16 +807,17 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO AddPacket(app, ack_req); return; } - + // if the program doesnt care about the status or if the status isnt what we requested if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) { // todo: save packets for later use AddPacket(app, ack_req); } - else - if(eqs) - eqs->QueuePacket(app, ack_req); + else if (eqs) + { + eqs->QueuePacket(app, ack_req); + } } void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) { @@ -827,7 +828,7 @@ void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CON return; } else { - if(eqs) + if(eqs) eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req); else if (app && (*app)) delete *app; @@ -1330,6 +1331,7 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num cm->chan_num = chan_num; strcpy(&cm->message[0], buffer); + QueuePacket(&app); bool senderCanTrainSelf = RuleB(Client, SelfLanguageLearning); diff --git a/zone/client.h b/zone/client.h index 57c4b54e1..7d02d4fe2 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2047,6 +2047,7 @@ private: bool CanTradeFVNoDropItem(); void SendMobPositions(); void PlayerTradeEventLog(Trade *t, Trade *t2); + }; #endif diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 054978986..a9b70d453 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -51,7 +51,7 @@ int32 Client::GetMaxStat() const else { base = 330; } - return (base); + return base; } int32 Client::GetMaxResist() const diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 80b726787..21140e337 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -25,6 +25,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include "bot.h" +#include "bot_command.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -606,6 +608,29 @@ void Client::CompleteConnect() if (raid) { SetRaidGrouped(true); raid->LearnMembers(); + std::list bots_list; + database.botdb.LoadBotsList(this->CharacterID(), bots_list); + std::vector r_members = raid->GetMembers(); + for (const RaidMember& iter : r_members) { + if (iter.membername) { + for (const BotsAvailableList& b_iter : bots_list) + { + if (strcmp(iter.membername, b_iter.Name) == 0) + { + char buffer[71] = "^spawn "; + strcat(buffer, iter.membername); + bot_command_real_dispatch(this, buffer); + Bot* b = entity_list.GetBotByBotName(iter.membername); + if (b) + { + b->SetRaidGrouped(true); + b->p_raid_instance = raid; + //b->SetFollowID(this->GetID()); + } + } + } + } + } raid->VerifyRaid(); raid->GetRaidDetails(); /* @@ -1534,27 +1559,27 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) group->SetNPCMarker(NPCMarkerName); group->SetGroupAAs(&GLAA); group->SetGroupMentor(mentor_percent, mentoree_name); - - //group->NotifyMainTank(this, 1); - //group->NotifyMainAssist(this, 1); - //group->NotifyPuller(this, 1); - - // If we are the leader, force an update of our group AAs to other members in the zone, in case - // we purchased a new one while out-of-zone. - if (group->IsLeader(this)) - group->SendLeadershipAAUpdate(); } + group->LearnMembers(); JoinGroupXTargets(group); group->UpdatePlayer(this); LFG = false; } + /* Load Bots */ if (RuleB(Bots, Enabled)) { database.botdb.LoadOwnerOptions(this); // TODO: mod below function for loading spawned botgroups Bot::LoadAndSpawnAllZonedBots(this); } + // If we are the leader, force an update of our group AAs to other members in the zone, in case + // we purchased a new one while out-of-zone. + // needs to be done after spawning bots + if (group && group->IsLeader(this)) { + group->SendLeadershipAAUpdate(); + } + m_inv.SetGMInventory((bool)m_pp.gm); // set to current gm state for calc CalcBonuses(); if (RuleB(Zone, EnableLoggedOffReplenishments) && @@ -2379,7 +2404,7 @@ void Client::Handle_OP_AdventureRequest(const EQApplicationPacket *app) if (IsRaidGrouped()) { int i = 0; - for (int x = 0; x < 72; ++x) + for (int x = 0; x < MAX_RAID_MEMBERS; ++x) { if (i == group_members) { @@ -2398,7 +2423,7 @@ void Client::Handle_OP_AdventureRequest(const EQApplicationPacket *app) else { int i = 0; - for (int x = 0; x < 6; ++x) + for (int x = 0; x < MAX_GROUP_MEMBERS; ++x) { if (i == group_members) { @@ -4194,10 +4219,6 @@ void Client::Handle_OP_Bug(const EQApplicationPacket *app) void Client::Handle_OP_Camp(const EQApplicationPacket *app) { - if (RuleB(Bots, Enabled)) { - Bot::BotOrderCampAll(this); - } - if (IsLFP()) worldserver.StopLFP(CharacterID()); @@ -6984,47 +7005,48 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app) GroupGeneric_Struct* gd = (GroupGeneric_Struct*)app->pBuffer; Raid *raid = entity_list.GetRaidByClient(this); - if (raid) - { - Mob* memberToDisband = nullptr; + if (raid) { + Mob *memberToDisband = nullptr; - if (!raid->IsGroupLeader(GetName())) + if (!raid->IsGroupLeader(GetName())) { memberToDisband = this; - else + } else { memberToDisband = GetTarget(); + } - if (!memberToDisband) + if (!memberToDisband) { memberToDisband = entity_list.GetMob(gd->name2); + } - if (!memberToDisband) + if (!memberToDisband) { memberToDisband = this; + } - if (!memberToDisband->IsClient()) + if (!memberToDisband->IsOfClientBot()) { return; - + } //we have a raid.. see if we're in a raid group uint32 grp = raid->GetGroup(memberToDisband->GetName()); bool wasGrpLdr = raid->members[raid->GetPlayerIndex(memberToDisband->GetName())].IsGroupLeader; if (grp < 12) { if (wasGrpLdr) { raid->SetGroupLeader(memberToDisband->GetName(), false); - for (int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if (raid->members[x].GroupNumber == grp) - { - if (strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, memberToDisband->GetName()) != 0) - { + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (raid->members[x].GroupNumber == grp) { + if (strlen(raid->members[x].membername) > 0 && + strcmp(raid->members[x].membername, memberToDisband->GetName()) != 0) { raid->SetGroupLeader(raid->members[x].membername); break; } } } } - raid->MoveMember(memberToDisband->GetName(), 0xFFFFFFFF); - raid->GroupUpdate(grp); //break - //raid->SendRaidGroupRemove(memberToDisband->GetName(), grp); - //raid->SendGroupUpdate(memberToDisband->CastToClient()); - raid->SendGroupDisband(memberToDisband->CastToClient()); + raid->MoveMember(memberToDisband->GetName(), RAID_GROUPLESS); + raid->GroupUpdate(grp); + + if (memberToDisband->IsClient()) { + raid->SendGroupDisband(memberToDisband->CastToClient()); + } } //we're done return; @@ -7032,8 +7054,9 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app) Group* group = GetGroup(); - if (!group) + if (!group) { return; + } // this block is necessary to allow more control over controlling how bots are zoned or camped. if (RuleB(Bots, Enabled) && Bot::GroupHasBot(group)) { @@ -7043,13 +7066,17 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app) } else { Mob* tempMember = entity_list.GetMob(gd->name1); //Name1 is the target you are disbanding + if (tempMember && tempMember->IsBot()) { - tempMember->CastToBot()->RemoveBotFromGroup(tempMember->CastToBot(), group); + auto b = tempMember->CastToBot(); + Bot::RemoveBotFromGroup(b, group); + if (LFP) { // If we are looking for players, update to show we are on our own now. UpdateLFP(); } + return; //No need to continue from here we were removing a bot from party } } @@ -7251,7 +7278,15 @@ void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app) } } else if (Invitee->IsBot()) { - Bot::ProcessBotGroupInvite(this, std::string(Invitee->GetName())); + Client* inviter = entity_list.GetClientByName(gis->inviter_name); + if (inviter && inviter->IsRaidGrouped() && !Invitee->HasRaid()) { + Bot::ProcessRaidInvite(Invitee->CastToBot(), inviter, true); + } + else if (!Invitee->HasRaid()) { + Bot::ProcessBotGroupInvite(this, std::string(Invitee->GetName())); + } else { + MessageString(Chat::LightGray, ALREADY_IN_RAID, Invitee->GetCleanName()); + } } } else @@ -11742,7 +11777,7 @@ void Client::Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app) } } -void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) +void Client::Handle_OP_RaidCommand(const EQApplicationPacket* app) { if (app->size < sizeof(RaidGeneral_Struct)) { LogError("Wrong size: OP_RaidCommand, size=[{}], expected at least [{}]", app->size, sizeof(RaidGeneral_Struct)); @@ -11750,18 +11785,42 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) return; } - RaidGeneral_Struct *raid_command_packet = (RaidGeneral_Struct*)app->pBuffer; + auto raid_command_packet = (RaidGeneral_Struct*)app->pBuffer; switch (raid_command_packet->action) { - case RaidCommandInviteIntoExisting: - case RaidCommandInvite: { + case RaidCommandInviteIntoExisting: + case RaidCommandInvite: { - Client *player_to_invite = entity_list.GetClientByName(raid_command_packet->player_name); + Bot* player_to_invite = nullptr; - if (!player_to_invite) + if (RuleB(Bots, Enabled) && entity_list.GetBotByBotName(raid_command_packet->player_name)) { + Bot* player_to_invite = entity_list.GetBotByBotName(raid_command_packet->player_name); + Group* player_to_invite_group = player_to_invite->GetGroup(); + + if (!player_to_invite) { break; + } - Group *player_to_invite_group = player_to_invite->GetGroup(); + if (player_to_invite_group && player_to_invite_group->IsGroupMember(this)) { + MessageString(Chat::Red, ALREADY_IN_PARTY); + break; + } + + if (player_to_invite->IsRaidGrouped()) { + MessageString(Chat::White, ALREADY_IN_RAID, player_to_invite->GetCleanName()); //must invite members not in raid... + return; + } + + Bot::ProcessRaidInvite(player_to_invite, this); + break; + + } else { + Client* player_to_invite = entity_list.GetClientByName(raid_command_packet->player_name); + if (!player_to_invite) { + break; + } + + Group* player_to_invite_group = player_to_invite->GetGroup(); if (player_to_invite->HasRaid()) { Message(Chat::Red, "%s is already in a raid.", player_to_invite->GetName()); @@ -11772,7 +11831,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) MessageString(Chat::Red, ALREADY_IN_PARTY); break; } - + // Not allowed: Invite a client that is in a group but not the groupleader if (player_to_invite_group && !player_to_invite_group->IsLeader(player_to_invite)) { Message(Chat::Red, "You can only invite an ungrouped player or group leader to join your raid."); break; @@ -11780,7 +11839,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) /* Send out invite to the client */ auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); - RaidGeneral_Struct *raid_command = (RaidGeneral_Struct*)outapp->pBuffer; + RaidGeneral_Struct* raid_command = (RaidGeneral_Struct*)outapp->pBuffer; strn0cpy(raid_command->leader_name, raid_command_packet->leader_name, 64); strn0cpy(raid_command->player_name, raid_command_packet->player_name, 64); @@ -11794,578 +11853,658 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) break; } - case RaidCommandAcceptInvite: { - Client *player_accepting_invite = entity_list.GetClientByName(raid_command_packet->player_name); - if (player_accepting_invite) { - if (IsRaidGrouped()) { - player_accepting_invite->MessageString(Chat::White, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... - return; + } + + case RaidCommandAcceptInvite: { + Client* player_sending_invite = entity_list.GetClientByName(raid_command_packet->player_name); + + // If the accepting client is in a group with a Bot or the invitor is in a group with a Bot, send the invite to Bot:ProcessRaidInvite + // instead of remaining here. + Bot* b = nullptr; + Client* invitee = entity_list.GetClientByName(raid_command_packet->leader_name); + Client* invitor = entity_list.GetClientByName(raid_command_packet->player_name); + + if (!invitee || !invitor) { + return; + } + + Group* g_invitee = invitee->GetGroup(); + Group* g_invitor = invitor->GetGroup(); + + if (invitee && invitee->IsRaidGrouped()) { + invitor->MessageString(Chat::White, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... + return; + } + + bool invitor_has_bot = false; + bool invitee_has_bot = false; + + if (RuleB(Bots, Enabled)) { + if (g_invitor && g_invitor->IsLeader(invitor)) { + for (const auto& g_invitor_member : g_invitor->members) { + if (g_invitor_member && g_invitor_member->IsBot()) { + invitee_has_bot = true; + } } - Raid *raid = entity_list.GetRaidByClient(player_accepting_invite); - if (raid) { - raid->VerifyRaid(); - Group *group = GetGroup(); - if (group) { - if (group->GroupCount() + raid->RaidCount() > MAX_RAID_MEMBERS) { - player_accepting_invite->Message(Chat::Red, "Invite failed, group invite would create a raid larger than the maximum number of members allowed."); - return; + } + if (g_invitee && g_invitee->IsLeader(invitee)) { + for (const auto& g_invitee_member : g_invitee->members) { + if (g_invitee_member && g_invitee_member->IsBot()) { + invitee_has_bot = true; + break; + } + } + } + if (invitor_has_bot || invitee_has_bot) { + Bot::ProcessRaidInvite(invitee, invitor); //two clients with one or both having groups with bots + break; + } + } + + if (player_sending_invite) { + if (IsRaidGrouped()) { + player_sending_invite->MessageString(Chat::White, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... + return; + } + Raid* raid = entity_list.GetRaidByClient(player_sending_invite); + if (raid) { + raid->VerifyRaid(); + Group* group = GetGroup(); + if (group) { + if (group->GroupCount() + raid->RaidCount() > MAX_RAID_MEMBERS) { + player_sending_invite->Message(Chat::Red, "Invite failed, group invite would create a raid larger than the maximum number of members allowed."); + return; + } + } + else { + if (1 + raid->RaidCount() > MAX_RAID_MEMBERS) { + player_sending_invite->Message(Chat::Red, "Invite failed, member invite would create a raid larger than the maximum number of members allowed."); + return; + } + } + if (group) {//add us all + uint32 free_group_id = raid->GetFreeGroup(); + Client* addClient = nullptr; + for (int x = 0; x < MAX_GROUP_MEMBERS; x++) { + if (group->members[x]) { + Client* c = nullptr; + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); + else + continue; + + if (!addClient) + { + addClient = c; + raid->SetGroupLeader(addClient->GetName()); + } + + raid->SendRaidCreate(c); + if (group->IsLeader(group->members[x])) + raid->AddMember(c, free_group_id, false, true); + else + raid->AddMember(c, free_group_id); + raid->SendBulkRaid(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } } } + group->JoinRaidXTarget(raid); + group->DisbandGroup(true); + raid->GroupUpdate(free_group_id); + } + else { + raid->SendRaidCreate(this); + raid->AddMember(this); + raid->SendBulkRaid(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); + } + } + } + else + { + auto player_sending_invite_group = player_sending_invite->GetGroup(); + Group* group = GetGroup(); + if (group) //if our target has a group + { + raid = new Raid(player_sending_invite); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + + uint32 raid_free_group_id = raid->GetFreeGroup(); + + /* If we already have a group then cycle through adding us... */ + if (player_sending_invite_group) { + Client* client_to_be_leader = nullptr; + for (const auto& sending_invite_member : player_sending_invite_group->members) { + if (sending_invite_member) { + if (!client_to_be_leader) { + if (sending_invite_member->IsClient()) { + client_to_be_leader = sending_invite_member->CastToClient(); + raid->SetGroupLeader(client_to_be_leader->GetName()); + } + } + if (player_sending_invite_group->IsLeader(sending_invite_member)) { + Client* c = nullptr; + + if (sending_invite_member->IsClient()) + c = sending_invite_member->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->AddMember(c, raid_free_group_id, true, true, true); + raid->SendBulkRaid(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + Client* c = nullptr; + + if (sending_invite_member->IsClient()) + c = sending_invite_member->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->AddMember(c, raid_free_group_id); + raid->SendBulkRaid(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + player_sending_invite_group->JoinRaidXTarget(raid, true); + player_sending_invite_group->DisbandGroup(true); + raid->GroupUpdate(raid_free_group_id); + raid_free_group_id = raid->GetFreeGroup(); + } else { - if (1 + raid->RaidCount() > MAX_RAID_MEMBERS) { - player_accepting_invite->Message(Chat::Red, "Invite failed, member invite would create a raid larger than the maximum number of members allowed."); - return; - } + raid->SendRaidCreate(player_sending_invite); + raid->AddMember(player_sending_invite, RAID_GROUPLESS, true, false, true); } - if (group) {//add us all - uint32 free_group_id = raid->GetFreeGroup(); - Client *addClient = nullptr; - for (int x = 0; x < 6; x++) { - if (group->members[x]) { - Client *c = nullptr; + + Client* client_to_add = nullptr; + /* Add client to an existing group */ + for (int x = 0; x < MAX_GROUP_MEMBERS; x++) { + if (group->members[x]) { + if (!client_to_add) { + if (group->members[x]->IsClient()) { + client_to_add = group->members[x]->CastToClient(); + raid->SetGroupLeader(client_to_add->GetName()); + } + } + if (group->IsLeader(group->members[x])) { + Client* c = nullptr; + if (group->members[x]->IsClient()) c = group->members[x]->CastToClient(); else continue; - if (!addClient) - { - addClient = c; - raid->SetGroupLeader(addClient->GetName()); + raid->SendRaidCreate(c); + raid->AddMember(c, raid_free_group_id, false, true); + raid->SendBulkRaid(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); } + } + else + { + Client* c = nullptr; + + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); + else + continue; raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - if (group->IsLeader(group->members[x])) - raid->AddMember(c, free_group_id, false, true); - else - raid->AddMember(c, free_group_id); + raid->AddMember(c, raid_free_group_id); raid->SendBulkRaid(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); if (raid->IsLocked()) { raid->SendRaidLockTo(c); } } } - group->JoinRaidXTarget(raid); - group->DisbandGroup(true); - raid->GroupUpdate(free_group_id); - } - else { - raid->SendRaidCreate(this); - raid->SendMakeLeaderPacketTo(raid->leadername, this); - raid->AddMember(this); - raid->SendBulkRaid(this); - if (raid->IsLocked()) { - raid->SendRaidLockTo(this); - } } + group->JoinRaidXTarget(raid); + group->DisbandGroup(true); + + raid->GroupUpdate(raid_free_group_id); } - else - { - Group *player_invited_group = player_accepting_invite->GetGroup(); - Group *group = GetGroup(); - if (group) //if our target has a group - { - raid = new Raid(player_accepting_invite); + /* Target does not have a group */ + else { + if (player_sending_invite_group) { + + raid = new Raid(player_sending_invite); + entity_list.AddRaid(raid); raid->SetRaidDetails(); - - uint32 raid_free_group_id = raid->GetFreeGroup(); - - /* If we already have a group then cycle through adding us... */ - if (player_invited_group) { - Client *client_to_be_leader = nullptr; - for (int x = 0; x < 6; x++) { - if (player_invited_group->members[x]) { - if (!client_to_be_leader) { - if (player_invited_group->members[x]->IsClient()) { - client_to_be_leader = player_invited_group->members[x]->CastToClient(); - raid->SetGroupLeader(client_to_be_leader->GetName()); - } - } - if (player_invited_group->IsLeader(player_invited_group->members[x])) { - Client *c = nullptr; - - if (player_invited_group->members[x]->IsClient()) - c = player_invited_group->members[x]->CastToClient(); - else - continue; - - raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, raid_free_group_id, true, true, true); - raid->SendBulkRaid(c); - - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } - } - else { - Client *c = nullptr; - - if (player_invited_group->members[x]->IsClient()) - c = player_invited_group->members[x]->CastToClient(); - else - continue; - - raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, raid_free_group_id); - raid->SendBulkRaid(c); - - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } + Client* addClientig = nullptr; + for (int x = 0; x < MAX_GROUP_MEMBERS; x++) { + if (player_sending_invite_group->members[x]) { + if (!addClientig) { + if (player_sending_invite_group->members[x]->IsClient()) { + addClientig = player_sending_invite_group->members[x]->CastToClient(); + raid->SetGroupLeader(addClientig->GetName()); } } - } - player_invited_group->JoinRaidXTarget(raid, true); - player_invited_group->DisbandGroup(true); - raid->GroupUpdate(raid_free_group_id); - raid_free_group_id = raid->GetFreeGroup(); - } - else { - raid->SendRaidCreate(player_accepting_invite); - raid->AddMember(player_accepting_invite, 0xFFFFFFFF, true, false, true); - } + if (player_sending_invite_group->IsLeader(player_sending_invite_group->members[x])) { + Client* c = nullptr; - Client *client_to_add = nullptr; - /* Add client to an existing group */ - for (int x = 0; x < 6; x++) { - if (group->members[x]) { - if (!client_to_add) { - if (group->members[x]->IsClient()) { - client_to_add = group->members[x]->CastToClient(); - raid->SetGroupLeader(client_to_add->GetName()); - } - } - if (group->IsLeader(group->members[x])) { - Client *c = nullptr; - - if (group->members[x]->IsClient()) - c = group->members[x]->CastToClient(); + if (player_sending_invite_group->members[x]->IsClient()) + c = player_sending_invite_group->members[x]->CastToClient(); else continue; raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, raid_free_group_id, false, true); + raid->AddMember(c, 0, true, true, true); raid->SendBulkRaid(c); - + raid->SendMakeLeaderPacketTo(raid->leadername, c); if (raid->IsLocked()) { raid->SendRaidLockTo(c); } } else { - Client *c = nullptr; - - if (group->members[x]->IsClient()) - c = group->members[x]->CastToClient(); + Client* c = nullptr; + if (player_sending_invite_group->members[x]->IsClient()) + c = player_sending_invite_group->members[x]->CastToClient(); else continue; raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, raid_free_group_id); + raid->AddMember(c, 0); raid->SendBulkRaid(c); - + raid->SendMakeLeaderPacketTo(raid->leadername, c); if (raid->IsLocked()) { raid->SendRaidLockTo(c); } } } } - group->JoinRaidXTarget(raid); - group->DisbandGroup(true); - - raid->GroupUpdate(raid_free_group_id); - } - /* Target does not have a group */ - else { - if (player_invited_group) { - - raid = new Raid(player_accepting_invite); - - entity_list.AddRaid(raid); - raid->SetRaidDetails(); - Client *addClientig = nullptr; - for (int x = 0; x < 6; x++) { - if (player_invited_group->members[x]) { - if (!addClientig) { - if (player_invited_group->members[x]->IsClient()) { - addClientig = player_invited_group->members[x]->CastToClient(); - raid->SetGroupLeader(addClientig->GetName()); - } - } - if (player_invited_group->IsLeader(player_invited_group->members[x])) { - Client *c = nullptr; - - if (player_invited_group->members[x]->IsClient()) - c = player_invited_group->members[x]->CastToClient(); - else - continue; - - raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, 0, true, true, true); - raid->SendBulkRaid(c); - - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } - } - else - { - Client *c = nullptr; - if (player_invited_group->members[x]->IsClient()) - c = player_invited_group->members[x]->CastToClient(); - else - continue; - - raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, 0); - raid->SendBulkRaid(c); - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } - } - } - } - raid->SendRaidCreate(this); - raid->SendMakeLeaderPacketTo(raid->leadername, this); - raid->SendBulkRaid(this); - player_invited_group->JoinRaidXTarget(raid, true); - raid->AddMember(this); - player_invited_group->DisbandGroup(true); - raid->GroupUpdate(0); - if (raid->IsLocked()) { - raid->SendRaidLockTo(this); - } + raid->SendRaidCreate(this); + raid->SendBulkRaid(this); + player_sending_invite_group->JoinRaidXTarget(raid, true); + raid->AddMember(this); + player_sending_invite_group->DisbandGroup(true); + raid->GroupUpdate(0); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); } - else { // neither has a group - raid = new Raid(player_accepting_invite); - entity_list.AddRaid(raid); - raid->SetRaidDetails(); - raid->SendRaidCreate(player_accepting_invite); - raid->SendRaidCreate(this); - raid->SendMakeLeaderPacketTo(raid->leadername, this); - raid->AddMember(player_accepting_invite, 0xFFFFFFFF, true, false, true); - raid->SendBulkRaid(this); - raid->AddMember(this); - if (raid->IsLocked()) { - raid->SendRaidLockTo(this); - } + } + else { // neither has a group + raid = new Raid(player_sending_invite); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + raid->SendRaidCreate(player_sending_invite); + raid->SendRaidCreate(this); + raid->AddMember(player_sending_invite, RAID_GROUPLESS, true, false, true); + raid->SendBulkRaid(this); + raid->AddMember(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); } } } } - break; } - case RaidCommandDisband: { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - uint32 group = raid->GetGroup(raid_command_packet->leader_name); + break; + } + case RaidCommandDisband: { + Raid* raid = entity_list.GetRaidByClient(this); + Client* c_to_disband = entity_list.GetClientByName(raid_command_packet->leader_name); + Client* c_doing_disband = entity_list.GetClientByName(raid_command_packet->player_name); - if (group < 12) { - uint32 i = raid->GetPlayerIndex(raid_command_packet->leader_name); - if (raid->members[i].IsGroupLeader) { //assign group leader to someone else - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (strlen(raid->members[x].membername) > 0 && i != x) { - if (raid->members[x].GroupNumber == group) { - raid->SetGroupLeader(raid_command_packet->leader_name, false); - raid->SetGroupLeader(raid->members[x].membername); - raid->UpdateGroupAAs(group); - break; - } - } - } + if (raid) { + raid->VerifyRaid(); + uint32 group = raid->GetGroup(raid_command_packet->leader_name); + if (RuleB(Bots, Enabled)) { + Bot* const b_to_disband = entity_list.GetBotByBotName(raid_command_packet->leader_name); + + //Added to remove all bots if the Bot_Owner is removed from the Raid + //Does not camp the Bots, just removes from the raid + if (c_to_disband) { + raid->HandleBotGroupDisband(c_to_disband->CharacterID()); + raid->RemoveMember(raid_command_packet->leader_name); + if (raid->IsLeader(c_to_disband->GetName())) { + uint32 i = raid->GetPlayerIndex(raid_command_packet->leader_name); + raid->SetNewRaidLeader(i); } - if (raid->members[i].IsRaidLeader) { - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid->members[i].membername) != 0) - { - raid->SetRaidLeader(raid->members[i].membername, raid->members[x].membername); - raid->UpdateRaidAAs(); - raid->SendAllRaidLeadershipAA(); + raid->SendGroupDisband(c_to_disband); + raid->GroupUpdate(group); + if (!raid->RaidCount() || !raid->GetLeader()) { + raid->DisbandRaid(); + } + break; + } else if (b_to_disband) { + uint32 gid = raid->GetGroup(b_to_disband->GetName()); + + if (gid < 12 && (raid->IsGroupLeader(b_to_disband->GetName()) || raid->GroupCount(gid) < 2)) { + uint32 owner_id = b_to_disband->CastToBot()->GetOwner()->CastToClient()->CharacterID(); + raid->HandleBotGroupDisband(owner_id, gid); + + } else if (b_to_disband && raid->IsRaidMember(b_to_disband->GetName())) { + Bot::RemoveBotFromRaid(b_to_disband); + + } else if (gid < 12 && raid->GetGroupLeader(gid) && raid->GetGroupLeader(gid)->IsBot()) { + c_doing_disband->Message( + Chat::Yellow, + fmt::format( + "{} is in a Bot Group. Please disband {} instead to remove the entire Bot group.", + raid_command_packet->leader_name, + raid->GetGroupLeader(gid)->CastToBot()->GetName() + ).c_str() + ); + } + + break; + } + } + if (group < 12) { + uint32 i = raid->GetPlayerIndex(raid_command_packet->leader_name); + if (raid->members[i].IsGroupLeader) { //assign group leader to someone else + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (strlen(raid->members[x].membername) > 0 && i != x) { + if (raid->members[x].GroupNumber == group) { + raid->SetGroupLeader(raid_command_packet->leader_name, false); + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(group); break; } } } } - - raid->RemoveMember(raid_command_packet->leader_name); - Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); - if (c) - raid->SendGroupDisband(c); - else { - auto pack = - new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - rga->zoneid = zone->GetZoneID(); - rga->instance_id = zone->GetInstanceID(); - strn0cpy(rga->playername, raid_command_packet->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); - } - //r->SendRaidGroupRemove(ri->leader_name, grp); - raid->GroupUpdate(group);// break - //} + raid->SetNewRaidLeader(i); } - break; + raid->RemoveMember(raid_command_packet->leader_name); + Client* c = entity_list.GetClientByName(raid_command_packet->leader_name); + if (c) { + + raid->SendGroupDisband(c); + } + else { + auto pack = + new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, raid_command_packet->leader_name, 64); + worldserver.SendPacket(pack); + safe_delete(pack); + } + raid->GroupUpdate(group);// break + if (!raid->RaidCount()) + raid->DisbandRaid(); } - case RaidCommandMoveGroup: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - /* Moving to group */ - if (raid_command_packet->parameter < 12) { - uint8 group_count = raid->GroupCount(raid_command_packet->parameter); + break; + } + case RaidCommandMoveGroup: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + /* Moving to group */ + if (raid_command_packet->parameter < 12) { + uint8 group_count = raid->GroupCount(raid_command_packet->parameter); - if (group_count < 6) { - Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); - uint32 old_group = raid->GetGroup(raid_command_packet->leader_name); - if (raid_command_packet->parameter == old_group) //don't rejoin grp if we order to join same group. - break; + if (group_count < 6) { + Client* c = entity_list.GetClientByName(raid_command_packet->leader_name); + uint32 old_group = raid->GetGroup(raid_command_packet->leader_name); + if (raid_command_packet->parameter == old_group) //don't rejoin grp if we order to join same group. + break; - if (raid->members[raid->GetPlayerIndex(raid_command_packet->leader_name)].IsGroupLeader) { - raid->SetGroupLeader(raid_command_packet->leader_name, false); - - /* We were the leader of our old group */ - if (old_group < 12) { - /* Assign new group leader if we can */ - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (raid->members[x].GroupNumber == old_group) { - if (strcmp(raid_command_packet->leader_name, raid->members[x].membername) != 0 && strlen(raid_command_packet->leader_name) > 0) { - raid->SetGroupLeader(raid->members[x].membername); - raid->UpdateGroupAAs(old_group); - - Client *client_to_update = entity_list.GetClientByName(raid->members[x].membername); - if (client_to_update) { - raid->SendRaidRemove(raid->members[x].membername, client_to_update); - raid->SendRaidCreate(client_to_update); - raid->SendMakeLeaderPacketTo(raid->leadername, client_to_update); - raid->SendRaidAdd(raid->members[x].membername, client_to_update); - raid->SendBulkRaid(client_to_update); - if (raid->IsLocked()) { - raid->SendRaidLockTo(client_to_update); - } - } - else { - auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *raid_command_packet = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - - raid_command_packet->rid = raid->GetID(); - raid_command_packet->zoneid = zone->GetZoneID(); - raid_command_packet->instance_id = zone->GetInstanceID(); - strn0cpy(raid_command_packet->playername, raid->members[x].membername, 64); - - worldserver.SendPacket(pack); - - safe_delete(pack); - } - break; - } - } - } - } - } - if (group_count == 0) { - raid->SetGroupLeader(raid_command_packet->leader_name); - raid->UpdateGroupAAs(raid_command_packet->parameter); - } - - raid->MoveMember(raid_command_packet->leader_name, raid_command_packet->parameter); - if (c) { - raid->SendGroupDisband(c); - } - else { - auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - raid_command->rid = raid->GetID(); - raid_command->zoneid = zone->GetZoneID(); - raid_command->instance_id = zone->GetInstanceID(); - strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); - } - - /* Send group update to our new group */ - raid->GroupUpdate(raid_command_packet->parameter); - - /* If our old was a group send update there too */ - if (old_group < 12) - raid->GroupUpdate(old_group); - - } - } - /* Move player to ungrouped bank */ - else { - Client *c = entity_list.GetClientByName(raid_command_packet->leader_name); - uint32 oldgrp = raid->GetGroup(raid_command_packet->leader_name); if (raid->members[raid->GetPlayerIndex(raid_command_packet->leader_name)].IsGroupLeader) { raid->SetGroupLeader(raid_command_packet->leader_name, false); - for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (raid->members[x].GroupNumber == oldgrp && strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid_command_packet->leader_name) != 0){ - raid->SetGroupLeader(raid->members[x].membername); - raid->UpdateGroupAAs(oldgrp); + /* We were the leader of our old group */ + if (old_group < 12) { + /* Assign new group leader if we can */ + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (raid->members[x].GroupNumber == old_group) { + if (strcmp(raid_command_packet->leader_name, raid->members[x].membername) != 0 && strlen(raid_command_packet->leader_name) > 0) { + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(old_group); - Client *client_leaving_group = entity_list.GetClientByName(raid->members[x].membername); - if (client_leaving_group) { - raid->SendRaidRemove(raid->members[x].membername, client_leaving_group); - raid->SendRaidCreate(client_leaving_group); - raid->SendMakeLeaderPacketTo(raid->leadername, client_leaving_group); - raid->SendRaidAdd(raid->members[x].membername, client_leaving_group); - raid->SendBulkRaid(client_leaving_group); - if (raid->IsLocked()) { - raid->SendRaidLockTo(client_leaving_group); + Client* client_to_update = entity_list.GetClientByName(raid->members[x].membername); + if (client_to_update) { + raid->SendRaidRemove(raid->members[x].membername, client_to_update); + raid->SendRaidCreate(client_to_update); + raid->SendMakeLeaderPacketTo(raid->leadername, client_to_update); + raid->SendRaidAdd(raid->members[x].membername, client_to_update); + raid->SendBulkRaid(client_to_update); + if (raid->IsLocked()) { + raid->SendRaidLockTo(client_to_update); + } + } + else { + auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* raid_command_packet = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + + raid_command_packet->rid = raid->GetID(); + raid_command_packet->zoneid = zone->GetZoneID(); + raid_command_packet->instance_id = zone->GetInstanceID(); + strn0cpy(raid_command_packet->playername, raid->members[x].membername, 64); + + worldserver.SendPacket(pack); + + safe_delete(pack); + } + break; } } - else { - auto pack = new ServerPacket( ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - - raid_command->rid = raid->GetID(); - strn0cpy(raid_command->playername, raid->members[x].membername, 64); - raid_command->zoneid = zone->GetZoneID(); - raid_command->instance_id = zone->GetInstanceID(); - - worldserver.SendPacket(pack); - safe_delete(pack); - } - break; } } } - raid->MoveMember(raid_command_packet->leader_name, 0xFFFFFFFF); + if (group_count == 0) { + raid->SetGroupLeader(raid_command_packet->leader_name); + raid->UpdateGroupAAs(raid_command_packet->parameter); + } + + raid->MoveMember(raid_command_packet->leader_name, raid_command_packet->parameter); if (c) { raid->SendGroupDisband(c); } else { auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - raid_command->rid = raid->GetID(); raid_command->zoneid = zone->GetZoneID(); raid_command->instance_id = zone->GetInstanceID(); strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); - worldserver.SendPacket(pack); - safe_delete(pack); } - raid->GroupUpdate(oldgrp); + /* Send group update to our new group */ + raid->GroupUpdate(raid_command_packet->parameter); + + /* If our old was a group send update there too */ + if (old_group < 12) + raid->GroupUpdate(old_group); + } } + /* Move player to ungrouped bank */ + else { + Client* c = entity_list.GetClientByName(raid_command_packet->leader_name); + uint32 oldgrp = raid->GetGroup(raid_command_packet->leader_name); + if (raid->members[raid->GetPlayerIndex(raid_command_packet->leader_name)].IsGroupLeader) { + raid->SetGroupLeader(raid_command_packet->leader_name, false); + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (raid->members[x].GroupNumber == oldgrp && strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid_command_packet->leader_name) != 0) { - Client *client_moved = entity_list.GetClientByName(raid_command_packet->leader_name); + raid->SetGroupLeader(raid->members[x].membername); + raid->UpdateGroupAAs(oldgrp); - if (client_moved && client_moved->GetRaid()) { - client_moved->GetRaid()->SendHPManaEndPacketsTo(client_moved); - client_moved->GetRaid()->SendHPManaEndPacketsFrom(client_moved); + Client* client_leaving_group = entity_list.GetClientByName(raid->members[x].membername); + if (client_leaving_group) { + raid->SendRaidRemove(raid->members[x].membername, client_leaving_group); + raid->SendRaidCreate(client_leaving_group); + raid->SendMakeLeaderPacketTo(raid->leadername, client_leaving_group); + raid->SendRaidAdd(raid->members[x].membername, client_leaving_group); + raid->SendBulkRaid(client_leaving_group); + if (raid->IsLocked()) { + raid->SendRaidLockTo(client_leaving_group); + } + } + else { + auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - Log(Logs::General, Logs::HPUpdate, - "Client::Handle_OP_RaidCommand :: %s sending and recieving HP/Mana/End updates", - client_moved->GetCleanName() - ); - } + raid_command->rid = raid->GetID(); + strn0cpy(raid_command->playername, raid->members[x].membername, 64); + raid_command->zoneid = zone->GetZoneID(); + raid_command->instance_id = zone->GetInstanceID(); - break; - } - case RaidCommandRaidLock: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - if (!raid->IsLocked()) - raid->LockRaid(true); - else - raid->SendRaidLockTo(this); - } - break; - } - case RaidCommandRaidUnlock: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) - { - if (raid->IsLocked()) - raid->LockRaid(false); - else - raid->SendRaidUnlockTo(this); - } - break; - } - case RaidCommandLootType2: - case RaidCommandLootType: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - Message(Chat::Yellow, "Loot type changed to: %d.", raid_command_packet->parameter); - raid->ChangeLootType(raid_command_packet->parameter); - } - break; - } - - case RaidCommandAddLooter2: - case RaidCommandAddLooter: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - Message(Chat::Yellow, "Adding %s as a raid looter.", raid_command_packet->leader_name); - raid->AddRaidLooter(raid_command_packet->leader_name); - } - break; - } - - case RaidCommandRemoveLooter2: - case RaidCommandRemoveLooter: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - Message(Chat::Yellow, "Removing %s as a raid looter.", raid_command_packet->leader_name); - raid->RemoveRaidLooter(raid_command_packet->leader_name); - } - break; - } - - case RaidCommandMakeLeader: - { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - if (strcmp(raid->leadername, GetName()) == 0) { - raid->SetRaidLeader(GetName(), raid_command_packet->leader_name); - raid->UpdateRaidAAs(); - raid->SendAllRaidLeadershipAA(); + worldserver.SendPacket(pack); + safe_delete(pack); + } + break; + } + } } + raid->MoveMember(raid_command_packet->leader_name, RAID_GROUPLESS); + if (c) { + raid->SendGroupDisband(c); + } + else { + auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + + raid_command->rid = raid->GetID(); + raid_command->zoneid = zone->GetZoneID(); + raid_command->instance_id = zone->GetInstanceID(); + strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); + + worldserver.SendPacket(pack); + + safe_delete(pack); + } + + raid->GroupUpdate(oldgrp); } - break; } - case RaidCommandSetMotd: + Client* client_moved = entity_list.GetClientByName(raid_command_packet->leader_name); + + if (client_moved && client_moved->GetRaid()) { + client_moved->GetRaid()->SendHPManaEndPacketsTo(client_moved); + client_moved->GetRaid()->SendHPManaEndPacketsFrom(client_moved); + + Log(Logs::General, Logs::HPUpdate, + "Client::Handle_OP_RaidCommand :: %s sending and recieving HP/Mana/End updates", + client_moved->GetCleanName() + ); + } + + break; + } + case RaidCommandRaidLock: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + if (!raid->IsLocked()) + raid->LockRaid(true); + else + raid->SendRaidLockTo(this); + } + break; + } + case RaidCommandRaidUnlock: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { - Raid *raid = entity_list.GetRaidByClient(this); - if (!raid) - break; - // we don't use the RaidGeneral here! - RaidMOTD_Struct *motd = (RaidMOTD_Struct *)app->pBuffer; - raid->SetRaidMOTD(std::string(motd->motd)); - raid->SaveRaidMOTD(); - raid->SendRaidMOTDToWorld(); - break; + if (raid->IsLocked()) + raid->LockRaid(false); + else + raid->SendRaidUnlockTo(this); } + break; + } + case RaidCommandLootType2: + case RaidCommandLootType: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(Chat::Yellow, "Loot type changed to: %d.", raid_command_packet->parameter); + raid->ChangeLootType(raid_command_packet->parameter); + } + break; + } - default: { - Message(Chat::Red, "Raid command (%d) NYI", raid_command_packet->action); - break; + case RaidCommandAddLooter2: + case RaidCommandAddLooter: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(Chat::Yellow, "Adding %s as a raid looter.", raid_command_packet->leader_name); + raid->AddRaidLooter(raid_command_packet->leader_name); } + break; + } + + case RaidCommandRemoveLooter2: + case RaidCommandRemoveLooter: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + Message(Chat::Yellow, "Removing %s as a raid looter.", raid_command_packet->leader_name); + raid->RemoveRaidLooter(raid_command_packet->leader_name); + } + break; + } + + case RaidCommandMakeLeader: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) { + if (strcmp(raid->leadername, GetName()) == 0) { + raid->SetRaidLeader(GetName(), raid_command_packet->leader_name); + raid->UpdateRaidAAs(); + raid->SendAllRaidLeadershipAA(); + } + } + break; + } + + case RaidCommandSetMotd: + { + Raid* raid = entity_list.GetRaidByClient(this); + if (!raid) + break; + // we don't use the RaidGeneral here! + RaidMOTD_Struct* motd = (RaidMOTD_Struct*)app->pBuffer; + raid->SetRaidMOTD(std::string(motd->motd)); + raid->SaveRaidMOTD(); + raid->SendRaidMOTDToWorld(); + break; + } + + default: { + Message(Chat::Red, "Raid command (%d) NYI", raid_command_packet->action); + break; + } } } + + void Client::Handle_OP_RandomReq(const EQApplicationPacket *app) { if (app->size != sizeof(RandomReq_Struct)) { diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 05986e691..01c92f75e 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -194,6 +194,9 @@ bool Client::Process() { } if (camp_timer.Check()) { + Raid* raid = entity_list.GetRaidByClient(this); + if (raid) + raid->RemoveMember(this->GetName()); LeaveGroup(); Save(); if (GetMerc()) diff --git a/zone/command.h b/zone/command.h index 6b00d2792..51ce0572c 100644 --- a/zone/command.h +++ b/zone/command.h @@ -204,7 +204,7 @@ void command_pvp(Client *c, const Seperator *sep); void command_qglobal(Client *c, const Seperator *sep); void command_questerrors(Client *c, const Seperator *sep); void command_race(Client *c, const Seperator *sep); -void command_raidloot(Client *c, const Seperator *sep); +void command_raidloot(Client* c, const Seperator* sep); void command_randomfeatures(Client *c, const Seperator *sep); void command_refreshgroup(Client *c, const Seperator *sep); void command_reload(Client *c, const Seperator *sep); diff --git a/zone/entity.cpp b/zone/entity.cpp index 51b2aec09..a447ec650 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -389,9 +389,7 @@ void EntityList::GroupProcess() for (auto &group : group_list) group->Process(); -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif + } void EntityList::QueueToGroupsForNPCHealthAA(Mob *sender, const EQApplicationPacket *app) @@ -609,9 +607,7 @@ void EntityList::AddGroup(Group *group) } AddGroup(group, gid); -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif + } void EntityList::AddGroup(Group *group, uint32 gid) @@ -2115,9 +2111,6 @@ Group *EntityList::GetGroupByMob(Mob *mob) return *iterator; ++iterator; } -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif return nullptr; } @@ -2132,9 +2125,6 @@ Group *EntityList::GetGroupByLeaderName(const char *leader) return *iterator; ++iterator; } -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif return nullptr; } @@ -2149,9 +2139,6 @@ Group *EntityList::GetGroupByID(uint32 group_id) return *iterator; ++iterator; } -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif return nullptr; } @@ -2170,13 +2157,12 @@ Group *EntityList::GetGroupByClient(Client *client) iterator = group_list.begin(); while (iterator != group_list.end()) { - if ((*iterator)->IsGroupMember(client->CastToMob())) + if ((*iterator)->IsGroupMember(client->CastToMob())) { return *iterator; + } ++iterator; } -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif + return nullptr; } @@ -2187,9 +2173,9 @@ Raid *EntityList::GetRaidByLeaderName(const char *leader) iterator = raid_list.begin(); while (iterator != raid_list.end()) { - if ((*iterator)->GetLeader()) - if(strcmp((*iterator)->GetLeader()->GetName(), leader) == 0) - return *iterator; + if ((*iterator)->GetLeader() && strcmp((*iterator)->GetLeader()->GetName(), leader) == 0) { + return *iterator; + } ++iterator; } return nullptr; @@ -2209,22 +2195,20 @@ Raid *EntityList::GetRaidByID(uint32 id) return nullptr; } -Raid *EntityList::GetRaidByClient(Client* client) +Raid* EntityList::GetRaidByClient(Client* client) { if (client->p_raid_instance) { return client->p_raid_instance; } - std::list::iterator iterator; + std::list::iterator iterator; iterator = raid_list.begin(); while (iterator != raid_list.end()) { - for (auto &member : (*iterator)->members) { - if (member.member) { - if (member.member == client) { - client->p_raid_instance = *iterator; - return *iterator; - } + for (const auto& member : (*iterator)->members) { + if (member.member && member.member == client) { + client->p_raid_instance = *iterator; + return *iterator; } } @@ -2233,6 +2217,49 @@ Raid *EntityList::GetRaidByClient(Client* client) return nullptr; } +Raid* EntityList::GetRaidByBotName(const char* name) +{ + std::list rm; + auto GetMembersWithNames = [&rm](Raid const* r) -> std::list { + for (const auto& m : r->members) { + if (strlen(m.membername) > 0) + rm.push_back(m); + } + return rm; + }; + + for (const auto& r : raid_list) { + for (const auto& m : GetMembersWithNames(r)) { + if (strcmp(m.membername, name) == 0) { + return r; + } + } + } + return nullptr; +} + +Raid* EntityList::GetRaidByBot(const Bot* bot) +{ + std::list rm; + auto GetMembersWhoAreBots = [&rm](Raid* r) -> std::list { + for (auto const& m : r->members) { + if (m.IsBot) { + rm.push_back(m); + } + } + return rm; + }; + + for (const auto& r : raid_list) { + for (const auto& m : GetMembersWhoAreBots(r)) { + if (m.member->CastToBot() == bot) { + return r; + } + } + } + return nullptr; +} + Raid *EntityList::GetRaidByMob(Mob *mob) { @@ -2658,6 +2685,12 @@ void EntityList::RemoveAllNPCs() npc_limit_list.clear(); } +void EntityList::RemoveAllBots() +{ + // doesn't clear the data + bot_list.clear(); +} + void EntityList::RemoveAllMercs() { // doesn't clear the data @@ -2671,9 +2704,6 @@ void EntityList::RemoveAllGroups() group_list.pop_front(); safe_delete(group); } -#if EQDEBUG >= 5 - CheckGroupList (__FILE__, __LINE__); -#endif } void EntityList::RemoveAllRaids() @@ -3081,6 +3111,8 @@ void EntityList::Clear() { RemoveAllClients(); entity_list.RemoveAllTraps(); //we can have child npcs so we go first + entity_list.RemoveAllMercs(); + entity_list.RemoveAllBots(); entity_list.RemoveAllNPCs(); entity_list.RemoveAllMobs(); entity_list.RemoveAllCorpses(); diff --git a/zone/entity.h b/zone/entity.h index ffd6c4c39..5a2939e60 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -192,7 +192,8 @@ public: Client* GetRandomClient(const glm::vec3& location = glm::vec3(0.f), float distance = 0, Client* exclude_client = nullptr); NPC* GetRandomNPC(const glm::vec3& location = glm::vec3(0.f), float distance = 0, NPC* exclude_npc = nullptr); Mob* GetRandomMob(const glm::vec3& location = glm::vec3(0.f), float distance = 0, Mob* exclude_mob = nullptr); - Group *GetGroupByMob(Mob* mob); + Group* GetGroupByMob(Mob* mob); + Group* GetGroupByBot(Bot* bot); bool IsInSameGroupOrRaidGroup(Client *client1, Client *client2); Group *GetGroupByClient(Client* client); Group *GetGroupByID(uint32 id); @@ -201,6 +202,8 @@ public: Raid *GetRaidByClient(Client* client); Raid *GetRaidByID(uint32 id); Raid *GetRaidByLeaderName(const char *leader); + Raid* GetRaidByBotName(const char* name); + Raid* GetRaidByBot(const Bot* bot); Corpse *GetCorpseByOwner(Client* client); Corpse *GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range); @@ -304,6 +307,7 @@ public: void RemoveAllMobs(); void RemoveAllClients(); void RemoveAllNPCs(); + void RemoveAllBots(); void RemoveAllMercs(); void RemoveAllGroups(); void RemoveAllCorpses(); @@ -619,7 +623,7 @@ private: bool RemoveBot(uint16 entityID); Mob* GetMobByBotID(uint32 botID); Bot* GetBotByBotID(uint32 botID); - Bot* GetBotByBotName(std::string botName); + Bot* GetBotByBotName(std::string_view botName); Client* GetBotOwnerByBotEntityID(uint32 entity_id); Client* GetBotOwnerByBotID(const uint32 bot_id); std::list GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID); diff --git a/zone/exp.cpp b/zone/exp.cpp index d1741329a..2907667d2 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -1200,7 +1200,7 @@ void Raid::SplitExp(const uint64 exp, Mob* other) { } for (const auto& m : members) { - if (m.member) { + if (m.member && !m.IsBot) { const int32 diff = m.member->GetLevel() - highest_level; int32 max_diff = -(m.member->GetLevel() * 15 / 10 - m.member->GetLevel()); diff --git a/zone/gm_commands/raidloot.cpp b/zone/gm_commands/raidloot.cpp index b75eb43c2..5bd3f3c8c 100755 --- a/zone/gm_commands/raidloot.cpp +++ b/zone/gm_commands/raidloot.cpp @@ -67,4 +67,3 @@ void command_raidloot(Client *c, const Seperator *sep) ).c_str() ); } - diff --git a/zone/groups.cpp b/zone/groups.cpp index bcf281151..a2b5bd3f3 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -9,7 +9,7 @@ but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software @@ -26,6 +26,7 @@ #include "worldserver.h" #include "string_ids.h" #include "../common/events/player_event_logs.h" +#include "../common/repositories/group_id_repository.h" extern EntityList entity_list; extern WorldServer worldserver; @@ -246,7 +247,7 @@ bool Group::AddMember(Mob* newmember, const char *NewMemberName, uint32 Characte uint32 i = 0; for (i = 0; i < MAX_GROUP_MEMBERS; ++i) { - if(!strcasecmp(membername[i], NewMemberName)) + if (!strcasecmp(membername[i], NewMemberName)) { return false; } @@ -495,7 +496,7 @@ void Group::SendEndurancePacketFrom(Mob* member) //updates a group member's client pointer when they zone in //if the group was in the zone already -bool Group::UpdatePlayer(Mob* update){ +bool Group::UpdatePlayer(Mob* update) { if (!update) return false; @@ -1138,29 +1139,30 @@ void Group::TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float } bool Group::LearnMembers() { - std::string query = StringFormat("SELECT name FROM group_id WHERE groupid = %lu", (unsigned long)GetID()); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - if (results.RowCount() == 0) { + auto rows = GroupIdRepository::GetWhere( + database, + fmt::format( + "groupid = {}", + GetID() + ) + ); + + if (rows.empty()) { LogError( - "Error getting group members for group [{}]: [{}]", - (unsigned long) GetID(), - results.ErrorMessage().c_str() + "Error getting group members for group [{}]", + GetID() ); - return false; } int memberIndex = 0; - for(auto row = results.begin(); row != results.end(); ++row) { - if(!row[0]) + for (const auto& member : rows) { + if (member.name.empty()) { continue; - + } members[memberIndex] = nullptr; - strn0cpy(membername[memberIndex], row[0], 64); - + strn0cpy(membername[memberIndex], member.name.c_str(), 64); memberIndex++; } @@ -1174,36 +1176,23 @@ void Group::VerifyGroup() { Only called every once in a while (on member re-join for now). */ - uint32 i; - for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { if (membername[i][0] == '\0') { -#if EQDEBUG >= 7 - LogDebug("Group [{}]: Verify [{}]: Empty.\n", (unsigned long)GetID(), i); -#endif members[i] = nullptr; continue; } Mob *them = entity_list.GetMob(membername[i]); if(them == nullptr && members[i] != nullptr) { //they aren't in zone -#if EQDEBUG >= 6 - LogDebug("Member of group [{}] named [{}] has disappeared!!", (unsigned long)GetID(), membername[i]); -#endif membername[i][0] = '\0'; members[i] = nullptr; continue; } if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good. -#if EQDEBUG >= 5 - LogDebug("Member of group [{}] named [{}] had an out of date pointer!!", (unsigned long)GetID(), membername[i]); -#endif members[i] = them; continue; } -#if EQDEBUG >= 8 - LogDebug("Member of group [{}] named [{}] is valid", (unsigned long)GetID(), membername[i]); -#endif } } @@ -2497,4 +2486,4 @@ bool Group::IsLeader(const char* name) { } return false; -} +} \ No newline at end of file diff --git a/zone/main.cpp b/zone/main.cpp index 083c2ff07..cb16f13aa 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -85,6 +85,7 @@ volatile bool RunLoops = true; #endif extern volatile bool is_zone_loaded; +extern bool Critical = false; #include "zone_event_scheduler.h" #include "../common/file.h" diff --git a/zone/mob.cpp b/zone/mob.cpp index 3e7d165ce..2c20fc05e 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3999,7 +3999,7 @@ void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::O const char *Mob::GetCleanName() { - if (!strlen(clean_name)) { + if (!strlen(clean_name)) { CleanMobName(GetName(), clean_name); } diff --git a/zone/mob.h b/zone/mob.h index c7415d092..30abe82d6 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1833,6 +1833,8 @@ private: EQ::InventoryProfile m_inv; std::shared_ptr m_target_of_heal_rotation; bool m_manual_follow; + + void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana); }; #endif diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index eb8ee55b6..685c65b26 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -369,7 +369,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates return false; } -bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { +bool NPC::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { LogAI("spellid [{}] tar [{}] mana [{}] Name [{}]", AIspells[i].spellid, tar->GetName(), mana_cost, spells[AIspells[i].spellid].name); casting_spell_AIindex = i; diff --git a/zone/npc.h b/zone/npc.h index 2335627a2..a33f649ad 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -589,7 +589,7 @@ protected: std::vector AIspells; bool HasAISpell; virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false); - virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); + virtual bool AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); AISpellsVar_Struct AISpellVar; int64 GetFocusEffect(focusType type, uint16 spell_id, Mob *caster = nullptr, bool from_buff_tic = false) override; uint16 innate_proc_spell_id; diff --git a/zone/raids.cpp b/zone/raids.cpp index af61925ed..2191b183a 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -26,6 +26,7 @@ #include "mob.h" #include "raids.h" #include "string_ids.h" +#include "bot.h" #include "worldserver.h" @@ -94,41 +95,57 @@ bool Raid::Process() } void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bool looter){ - if(!c) + if (!c) { return; + } - std::string query = StringFormat("INSERT INTO raid_members SET raidid = %lu, charid = %lu, " - "groupid = %lu, _class = %d, level = %d, name = '%s', " - "isgroupleader = %d, israidleader = %d, islooter = %d", - (unsigned long)GetID(), (unsigned long)c->CharacterID(), - (unsigned long)group, c->GetClass(), c->GetLevel(), - c->GetName(), groupleader, rleader, looter); - auto results = database.QueryDatabase(query); + const auto query = fmt::format( + "REPLACE INTO raid_members SET raidid = {}, charid = {}, bot_id = 0, " + "groupid = {}, _class = {}, level = {}, name = '{}', " + "isgroupleader = {}, israidleader = {}, islooter = {}", + GetID(), + c->CharacterID(), + group, + c->GetClass(), + c->GetLevel(), + c->GetName(), + groupleader, + rleader, + looter + ); + auto results = database.QueryDatabase(query); if(!results.Success()) { LogError("Error inserting into raid members: [{}]", results.ErrorMessage().c_str()); } LearnMembers(); VerifyRaid(); + if (rleader) { + database.SetRaidGroupLeaderInfo(group, GetID()); + UpdateRaidAAs(); + } else if (rleader) { database.SetRaidGroupLeaderInfo(RAID_GROUPLESS, GetID()); UpdateRaidAAs(); } + if (group != RAID_GROUPLESS && groupleader) { database.SetRaidGroupLeaderInfo(group, GetID()); UpdateGroupAAs(group); } - if(group < 12) + + if (group < MAX_RAID_GROUPS) { GroupUpdate(group); - else // get raid AAs, GroupUpdate will handles it otherwise + } else { // get raid AAs, GroupUpdate will handles it otherwise SendGroupLeadershipAA(c, RAID_GROUPLESS); + } + SendRaidAddAll(c->GetName()); c->SetRaidGrouped(true); SendRaidMOTD(c); - // xtarget shit .......... if (group == RAID_GROUPLESS) { if (rleader) { GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr()); @@ -148,54 +165,115 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo } } - Raid *raid_update = nullptr; - raid_update = c->GetRaid(); + auto* raid_update = c->GetRaid(); if (raid_update) { raid_update->SendHPManaEndPacketsTo(c); raid_update->SendHPManaEndPacketsFrom(c); } auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - strn0cpy(rga->playername, c->GetName(), 64); - rga->zoneid = zone->GetZoneID(); + auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer; + strn0cpy(rga->playername, c->GetName(), sizeof(rga->playername)); + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); rga->instance_id = zone->GetInstanceID(); worldserver.SendPacket(pack); safe_delete(pack); } + + +void Raid::AddBot(Bot* b, uint32 group, bool rleader, bool groupleader, bool looter) { + if (!b) { + return; + } + + const auto query = fmt::format( + "REPLACE INTO raid_members SET raidid = {}, " + "charid = 0, bot_id = {}, groupid = {}, _class = {}, level = {}, name = '{}', " + "isgroupleader = {}, israidleader = {}, islooter = {}", + GetID(), + b->GetBotID(), + group, + b->GetClass(), + b->GetLevel(), + b->GetName(), + groupleader, + rleader, + looter + ); + + auto results = database.QueryDatabase(query); + + LearnMembers(); + VerifyRaid(); + + if (group < MAX_RAID_GROUPS) { + GroupUpdate(group); + } else { // get raid AAs, GroupUpdate will handle it otherwise + SendGroupLeadershipAA(b->GetOwner()->CastToClient(), RAID_GROUPLESS); + } + + SendRaidAddAll(b->GetName()); + + b->SetRaidGrouped(true); + b->p_raid_instance = this; + + + auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); + auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer; + strn0cpy(rga->playername, b->GetName(), sizeof(rga->playername)); + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); +} + + void Raid::RemoveMember(const char *characterName) { std::string query = StringFormat("DELETE FROM raid_members where name='%s'", characterName); auto results = database.QueryDatabase(query); - Client *client = entity_list.GetClientByName(characterName); + auto* b = entity_list.GetBotByBotName(characterName); + auto* c = entity_list.GetClientByName(characterName); + + if (RuleB(Bots, Enabled) && b) { + b = entity_list.GetBotByBotName(characterName); + b->SetFollowID(b->GetOwner()->CastToClient()->GetID()); + b->SetTarget(nullptr); + b->SetRaidGrouped(false); + } + disbandCheck = true; SendRaidRemoveAll(characterName); - SendRaidDisband(client); + SendRaidDisband(c); LearnMembers(); VerifyRaid(); - if(client) { - client->SetRaidGrouped(false); - client->LeaveRaidXTargets(this); - client->p_raid_instance = nullptr; + if (c) { + c->SetRaidGrouped(false); + c->LeaveRaidXTargets(this); + c->p_raid_instance = nullptr; } auto pack = new ServerPacket(ServerOP_RaidRemove, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); + auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); rga->instance_id = zone->GetInstanceID(); - strn0cpy(rga->playername, characterName, 64); - rga->zoneid = zone->GetZoneID(); + rga->zoneid = zone->GetZoneID(); + strn0cpy(rga->playername, characterName, sizeof(rga->playername)); worldserver.SendPacket(pack); safe_delete(pack); } void Raid::DisbandRaid() { - std::string query = StringFormat("DELETE FROM raid_members WHERE raidid = %lu", (unsigned long)GetID()); + const auto query = fmt::format( + "DELETE FROM raid_members WHERE raidid = {}", + GetID() + ); auto results = database.QueryDatabase(query); LearnMembers(); @@ -203,11 +281,11 @@ void Raid::DisbandRaid() SendRaidDisbandAll(); auto pack = new ServerPacket(ServerOP_RaidDisband, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - strn0cpy(rga->playername, " ", 64); - rga->zoneid = zone->GetZoneID(); + auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); rga->instance_id = zone->GetInstanceID(); + strn0cpy(rga->playername, " ", sizeof(rga->playername)); worldserver.SendPacket(pack); safe_delete(pack); @@ -216,19 +294,23 @@ void Raid::DisbandRaid() void Raid::MoveMember(const char *name, uint32 newGroup) { - std::string query = StringFormat("UPDATE raid_members SET groupid = %lu WHERE name = '%s'", - (unsigned long)newGroup, name); - auto results = database.QueryDatabase(query); + + const auto query = fmt::format( + "UPDATE raid_members SET groupid = {} WHERE name = '{}'", + newGroup, + name + ); + auto results = database.QueryDatabase(query); LearnMembers(); VerifyRaid(); SendRaidMoveAll(name); - + auto pack = new ServerPacket(ServerOP_RaidChangeGroup, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - strn0cpy(rga->playername, name, 64); - rga->zoneid = zone->GetZoneID(); + auto* rga = (ServerRaidGeneralAction_Struct*) pack->pBuffer; + strn0cpy(rga->playername, name, sizeof(rga->playername)); + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); rga->instance_id = zone->GetInstanceID(); worldserver.SendPacket(pack); safe_delete(pack); @@ -236,18 +318,21 @@ void Raid::MoveMember(const char *name, uint32 newGroup) void Raid::SetGroupLeader(const char *who, bool glFlag) { - std::string query = StringFormat("UPDATE raid_members SET isgroupleader = %lu WHERE name = '%s'", - (unsigned long)glFlag, who); - auto results = database.QueryDatabase(query); + const auto query = fmt::format( + "UPDATE raid_members SET isgroupleader = {} WHERE name = '{}'", + glFlag, + who + ); + auto results = database.QueryDatabase(query); LearnMembers(); VerifyRaid(); auto pack = new ServerPacket(ServerOP_RaidGroupLeader, sizeof(ServerRaidGeneralAction_Struct)); - ServerRaidGeneralAction_Struct *rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - rga->rid = GetID(); - strn0cpy(rga->playername, who, 64); - rga->zoneid = zone->GetZoneID(); + auto* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + strn0cpy(rga->playername, who, sizeof(rga->playername)); + rga->rid = GetID(); + rga->zoneid = zone->GetZoneID(); rga->instance_id = zone->GetInstanceID(); worldserver.SendPacket(pack); safe_delete(pack); @@ -406,13 +491,13 @@ uint32 Raid::GetFreeGroup() return x; } //if we get to here then there were no free groups so we added the group as free floating members. - return 0xFFFFFFFF; + return RAID_GROUPLESS; } uint8 Raid::GroupCount(uint32 gid) { uint8 count = 0; - if(gid < 12) + if(gid < MAX_RAID_GROUPS) { for(int x = 0; x < MAX_RAID_MEMBERS; x++) { @@ -453,7 +538,7 @@ uint32 Raid::GetGroup(const char *name) if(strcmp(members[x].membername, name) == 0) return members[x].GroupNumber; } - return 0xFFFFFFFF; + return RAID_GROUPLESS; } uint32 Raid::GetGroup(Client *c) @@ -463,7 +548,7 @@ uint32 Raid::GetGroup(Client *c) if(members[x].member == c) return members[x].GroupNumber; } - return 0xFFFFFFFF; + return RAID_GROUPLESS; } void Raid::RaidSay(const char *msg, Client *c, uint8 language, uint8 lang_skill) @@ -474,7 +559,7 @@ void Raid::RaidSay(const char *msg, Client *c, uint8 language, uint8 lang_skill) auto pack = new ServerPacket(ServerOP_RaidSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1); ServerRaidMessage_Struct *rga = (ServerRaidMessage_Struct*)pack->pBuffer; rga->rid = GetID(); - rga->gid = 0xFFFFFFFF; + rga->gid = RAID_GROUPLESS; rga->language = language; rga->lang_skill = lang_skill; strn0cpy(rga->from, c->GetName(), 64); @@ -992,19 +1077,21 @@ void Raid::SendRaidAdd(const char *who, Client *to) if(!to) return; - for(int x = 0; x < MAX_RAID_MEMBERS; x++) + std::vector rm = GetMembers(); + + for (const auto& m : rm) { - if(strcmp(members[x].membername, who) == 0) + if (strcmp(m.membername, who) == 0) { auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidAddMember_Struct)); - RaidAddMember_Struct *ram = (RaidAddMember_Struct*)outapp->pBuffer; + RaidAddMember_Struct* ram = (RaidAddMember_Struct*)outapp->pBuffer; ram->raidGen.action = raidAdd; - ram->raidGen.parameter = members[x].GroupNumber; - strn0cpy(ram->raidGen.leader_name, members[x].membername, 64); - strn0cpy(ram->raidGen.player_name, members[x].membername, 64); - ram->_class = members[x]._class; - ram->level = members[x].level; - ram->isGroupLeader = members[x].IsGroupLeader; + ram->raidGen.parameter = m.GroupNumber; + strn0cpy(ram->raidGen.leader_name, m.membername, 64); + strn0cpy(ram->raidGen.player_name, m.membername, 64); + ram->_class = m._class; + ram->level = m.level; + ram->isGroupLeader = m.IsGroupLeader; to->QueuePacket(outapp); safe_delete(outapp); return; @@ -1014,19 +1101,21 @@ void Raid::SendRaidAdd(const char *who, Client *to) void Raid::SendRaidAddAll(const char *who) { - for(int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if(strcmp(members[x].membername, who) == 0) + std::vector rm = GetMembers(); + + for (const auto& m : rm) { + if (strcmp(m.membername, who) == 0) { auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidAddMember_Struct)); - RaidAddMember_Struct *ram = (RaidAddMember_Struct*)outapp->pBuffer; + RaidAddMember_Struct* ram = (RaidAddMember_Struct*)outapp->pBuffer; ram->raidGen.action = raidAdd; - ram->raidGen.parameter = members[x].GroupNumber; - strcpy(ram->raidGen.leader_name, members[x].membername); - strcpy(ram->raidGen.player_name, members[x].membername); - ram->_class = members[x]._class; - ram->level = members[x].level; - ram->isGroupLeader = members[x].IsGroupLeader; + ram->raidGen.parameter = m.GroupNumber; + strcpy(ram->raidGen.leader_name, m.membername); + strcpy(ram->raidGen.player_name, m.membername); + ram->isGroupLeader = m.IsGroupLeader; + ram->_class = m._class; + ram->level = m.level; + QueuePacket(outapp); safe_delete(outapp); return; @@ -1129,12 +1218,13 @@ void Raid::SendRaidMoveAll(const char* who) SendRaidRemoveAll(who); if(c) SendRaidCreate(c); - SendMakeLeaderPacket(leadername); - SendRaidAddAll(who); if(c){ SendBulkRaid(c); if(IsLocked()) { SendRaidLockTo(c); } } + SendRaidAddAll(who); + SendMakeLeaderPacket(leadername); + } void Raid::SendBulkRaid(Client *to) @@ -1144,7 +1234,7 @@ void Raid::SendBulkRaid(Client *to) for(int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(strlen(members[x].membername) > 0 && (strcmp(members[x].membername, to->GetName()) != 0)) //don't send ourself + if (strlen(members[x].membername) > 0 && (strcmp(members[x].membername, to->GetName()) != 0)) //don't send ourself { SendRaidAdd(members[x].membername, to); } @@ -1155,7 +1245,7 @@ void Raid::QueuePacket(const EQApplicationPacket *app, bool ack_req) { for(int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(members[x].member) + if(members[x].member && !members[x].IsBot) { members[x].member->QueuePacket(app, ack_req); } @@ -1164,6 +1254,11 @@ void Raid::QueuePacket(const EQApplicationPacket *app, bool ack_req) void Raid::SendMakeLeaderPacket(const char *who) //30 { + + if (RuleB(Bots, Enabled) && entity_list.GetBotByBotName(who) && members[GetPlayerIndex(who)].IsBot) { + return; + } + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct)); RaidLeadershipUpdate_Struct *rg = (RaidLeadershipUpdate_Struct*)outapp->pBuffer; rg->action = raidMakeLeader; @@ -1176,8 +1271,13 @@ void Raid::SendMakeLeaderPacket(const char *who) //30 void Raid::SendMakeLeaderPacketTo(const char *who, Client *to) { - if(!to) + if (!to) { return; + } + + if (RuleB(Bots, Enabled) && members[GetPlayerIndex(who)].IsBot) { + return; + } auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct)); RaidLeadershipUpdate_Struct *rg = (RaidLeadershipUpdate_Struct*)outapp->pBuffer; @@ -1206,8 +1306,13 @@ void Raid::SendMakeGroupLeaderPacketTo(const char *who, Client *to) void Raid::SendGroupUpdate(Client *to) { - if(!to) + if (!to) { return; + } + + if (RuleB(Bots, Enabled) && members[GetPlayerIndex(to)].IsBot) { + return; + } auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct)); GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer; @@ -1249,13 +1354,14 @@ void Raid::SendGroupUpdate(Client *to) void Raid::GroupUpdate(uint32 gid, bool initial) { - if(gid > 11) //ungrouped member doesn't need grouping. + if(gid > 11) {//ungrouped member doesn't need grouping. return; - for(int x = 0; x < MAX_RAID_MEMBERS; x++) + } + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { if(strlen(members[x].membername) > 0){ if(members[x].GroupNumber == gid){ - if(members[x].member) { + if (members[x].member) { SendGroupUpdate(members[x].member); SendGroupLeadershipAA(members[x].member, gid); } @@ -1362,6 +1468,10 @@ void Raid::SendRaidMOTD(Client *c) if (!c || motd.empty()) return; + if (members[GetPlayerIndex(c)].IsBot) { + return; + } + size_t size = motd.size() + 1; auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidMOTD_Struct) + size); RaidMOTD_Struct *rmotd = (RaidMOTD_Struct *)outapp->pBuffer; @@ -1397,6 +1507,10 @@ void Raid::SendRaidMOTDToWorld() void Raid::SendGroupLeadershipAA(Client *c, uint32 gid) { + if (RuleB(Bots, Enabled) && members[GetPlayerIndex(c)].IsBot) { + return; + } + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct)); RaidLeadershipUpdate_Struct *rlaa = (RaidLeadershipUpdate_Struct *)outapp->pBuffer; rlaa->action = raidSetLeaderAbilities; @@ -1411,18 +1525,23 @@ void Raid::SendGroupLeadershipAA(Client *c, uint32 gid) void Raid::SendGroupLeadershipAA(uint32 gid) { - for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) - if (members[i].member && members[i].GroupNumber == gid) - SendGroupLeadershipAA(members[i].member, gid); + for (const auto& m : members) { + if (m.member && m.GroupNumber == gid && !m.IsBot) { + SendGroupLeadershipAA(m.member, gid); + } + } } void Raid::SendAllRaidLeadershipAA() { - for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) - if (members[i].member) - SendGroupLeadershipAA(members[i].member, members[i].GroupNumber); + for (const auto& m : members) { + if (m.member && !m.IsBot) { + SendGroupLeadershipAA(m.member, m.GroupNumber); + } + } } + void Raid::LockRaid(bool lockFlag) { std::string query = StringFormat("UPDATE raid_details SET locked = %d WHERE raidid = %lu", @@ -1486,70 +1605,84 @@ void Raid::SaveRaidMOTD() bool Raid::LearnMembers() { - memset(members, 0, (sizeof(RaidMember)*MAX_RAID_MEMBERS)); + memset(members, 0, (sizeof(RaidMember) * MAX_RAID_MEMBERS)); - std::string query = StringFormat("SELECT name, groupid, _class, level, " - "isgroupleader, israidleader, islooter " - "FROM raid_members WHERE raidid = %lu", - (unsigned long)GetID()); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; + const auto query = fmt::format( + "SELECT name, groupid, _class, level, " + "isgroupleader, israidleader, islooter, bot_id " + "FROM raid_members WHERE raidid = {} ORDER BY groupid", + GetID() + ); - if(results.RowCount() == 0) { - LogError("Error getting raid members for raid [{}]: [{}]", (unsigned long)GetID(), results.ErrorMessage().c_str()); - disbandCheck = true; - return false; - } + auto results = database.QueryDatabase(query); + if (!results.Success()) { + return false; + } - int index = 0; - for(auto row = results.begin(); row != results.end(); ++row) { - if(!row[0]) - continue; + if (!results.RowCount()) { + LogError("Error getting raid members for raid [{}]: [{}]", GetID(), results.ErrorMessage()); + disbandCheck = true; + return false; + } - members[index].member = nullptr; - strn0cpy(members[index].membername, row[0], 64); - uint32 groupNum = Strings::ToUnsignedInt(row[1]); - if(groupNum > 11) - members[index].GroupNumber = 0xFFFFFFFF; - else - members[index].GroupNumber = groupNum; + int index = 0; + for (auto row: results) { + if (!row[0]) { + continue; + } - members[index]._class = Strings::ToInt(row[2]); - members[index].level = Strings::ToInt(row[3]); - members[index].IsGroupLeader = Strings::ToInt(row[4]); - members[index].IsRaidLeader = Strings::ToInt(row[5]); - members[index].IsLooter = Strings::ToInt(row[6]); - ++index; - } + members[index].member = nullptr; + strn0cpy(members[index].membername, row[0], sizeof(members[index].membername)); + uint32 group_id = Strings::ToUnsignedInt(row[1]); + if (group_id > 11) { + members[index].GroupNumber = RAID_GROUPLESS; + } else { + members[index].GroupNumber = group_id; + } + + members[index]._class = Strings::ToUnsignedInt(row[2]); + members[index].level = Strings::ToUnsignedInt(row[3]); + members[index].IsGroupLeader = Strings::ToBool(row[4]); + members[index].IsRaidLeader = Strings::ToBool(row[5]); + members[index].IsLooter = Strings::ToBool(row[6]); + members[index].IsBot = Strings::ToBool(row[7]) > 0; + ++index; + } + return true; } void Raid::VerifyRaid() { - for(int x = 0; x < MAX_RAID_MEMBERS; x++) - { - if(strlen(members[x].membername) == 0){ - members[x].member = nullptr; - } - else{ - Client *c = entity_list.GetClientByName(members[x].membername); - if(c){ - members[x].member = c; - } - else{ - members[x].member = nullptr; + for (auto& m : members) { + if(strlen(m.membername) == 0){ + m.member = nullptr; + } else { + auto* c = entity_list.GetClientByName(m.membername); + auto* b = entity_list.GetBotByBotName(m.membername); + + if (c) { + m.member = c; + m.IsBot = false; + } else if(RuleB(Bots, Enabled) && b){ + //Raid requires client* we are forcing it here to be a BOT. Care is needed here as any client function that + //does not exist within the Bot Class will likely corrupt memory for the member object. Good practice to test the IsBot + //attribute before calling a client function or casting to client. + b = entity_list.GetBotByBotName(m.membername); + m.member = b->CastToClient(); + m.IsBot = true; //Used to identify those members who are Bots + } else { + m.member = nullptr; + m.IsBot = false; } } - if(members[x].IsRaidLeader){ - if(strlen(members[x].membername) > 0){ - SetLeader(members[x].member); - strn0cpy(leadername, members[x].membername, 64); - } - else - { - //should never happen, but maybe it is? + + if (m.IsRaidLeader) { + if (strlen(m.membername) > 0){ + SetLeader(m.member); + strn0cpy(leadername, m.membername, sizeof(leadername)); + } else { SetLeader(nullptr); } } @@ -1577,7 +1710,7 @@ void Raid::MemberZoned(Client *c) } } - if (gid < 12 && group_mentor[gid].mentoree == c) + if (gid < MAX_RAID_GROUPS && group_mentor[gid].mentoree == c) group_mentor[gid].mentoree = nullptr; } @@ -1592,7 +1725,7 @@ void Raid::SendHPManaEndPacketsTo(Client *client) EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); for(int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(members[x].member) { + if(members[x].member && !members[x].IsBot) { if((members[x].member != client) && (members[x].GroupNumber == group_id)) { members[x].member->CreateHPPacket(&hp_packet); @@ -1634,7 +1767,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) mob->CreateHPPacket(&hpapp); for(int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(members[x].member) { + if(members[x].member && !members[x].IsBot) { if(!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { members[x].member->QueuePacket(&hpapp, false); if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) { @@ -1667,7 +1800,7 @@ void Raid::SendManaPacketFrom(Mob *mob) EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (members[x].member) { + if (members[x].member && !members[x].IsBot) { if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) { outapp.SetOpcode(OP_MobManaUpdate); @@ -1694,7 +1827,7 @@ void Raid::SendEndurancePacketFrom(Mob *mob) EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (members[x].member) { + if (members[x].member && !members[x].IsBot) { if (!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { if (members[x].member->ClientVersion() >= EQ::versions::ClientVersion::SoD) { outapp.SetOpcode(OP_MobEnduranceUpdate); @@ -1801,9 +1934,14 @@ void Raid::CheckGroupMentor(uint32 group_id, Client *c) void Raid::SetDirtyAutoHaters() { for (int i = 0; i < MAX_RAID_MEMBERS; ++i) - if (members[i].member) + if (members[i].member && members[i].IsBot) + { + members[i].member->CastToBot()->SetDirtyAutoHaters(); + } + else if (members[i].member && !members[i].IsBot) + { members[i].member->SetDirtyAutoHaters(); - + } } void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/, bool group_only /*= true*/) { @@ -1812,22 +1950,25 @@ void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_re uint32 group_id = GetGroup(sender->CastToClient()); /* If this is a group only packet and we're not in a group -- return */ - if (group_id == 0xFFFFFFFF && group_only) + if (group_id == RAID_GROUPLESS && group_only) return; for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) { - if (!members[i].member) + if (!members[i].member) { continue; - - if (!members[i].member->IsClient()) + } + if (!members[i].member->IsClient()) { continue; - - if (ignore_sender && members[i].member == sender) + } + if (members[i].IsBot) { continue; - - if (group_only && members[i].GroupNumber != group_id) + } + if (ignore_sender && members[i].member == sender) { continue; - + } + if (group_only && members[i].GroupNumber != group_id) { + continue; + } /* If we don't have a distance requirement - send to all members */ if (distance == 0) { members[i].member->CastToClient()->QueuePacket(app, ack_required); @@ -1884,3 +2025,84 @@ bool Raid::DoesAnyMemberHaveExpeditionLockout( return Expedition::HasLockoutByCharacterName(raid_member.membername, expedition_name, event_name); }); } + +Mob* Raid::GetRaidMainAssistOneByName(const char* name) +{ + Raid* raid = entity_list.GetRaidByBotName(name); + std::vector raid_members = raid->GetMembers(); + + for (RaidMember iter : raid_members) + { + if (iter.IsRaidMainAssistOne) { + return iter.member->CastToMob(); + } + } + return nullptr; +} +bool Raid::IsEngaged() { + + std::vector rm = GetMembers(); + for (const auto& m : rm) { + if (m.member && !m.IsBot && (m.member->IsEngaged() || m.member->GetAggroCount() > 0)) { + return 1; + } + } + return 0; +} +void Raid::RaidGroupSay(const char* msg, const char* from, uint8 language, uint8 lang_skill) +{ + if (!from) + return; + + uint32 groupToUse = GetGroup(from); + + if (groupToUse > 11) + return; + + auto pack = new ServerPacket(ServerOP_RaidGroupSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1); + ServerRaidMessage_Struct* rga = (ServerRaidMessage_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->gid = groupToUse; + rga->language = language; + rga->lang_skill = lang_skill; + strn0cpy(rga->from, from, 64); + + strcpy(rga->message, msg); // this is safe because we are allocating enough space for the entire msg above + + worldserver.SendPacket(pack); + safe_delete(pack); +} +void Raid::RaidSay(const char* msg, const char* from, uint8 language, uint8 lang_skill) +{ + if (!from) + return; + + auto pack = new ServerPacket(ServerOP_RaidSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1); + ServerRaidMessage_Struct* rga = (ServerRaidMessage_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->gid = RAID_GROUPLESS; + rga->language = language; + rga->lang_skill = lang_skill; + strn0cpy(rga->from, from, 64); + + strcpy(rga->message, msg); + + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void Raid::SetNewRaidLeader(uint32 i) +{ + if (members[i].IsRaidLeader) { + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { + if (!members[x].IsBot) { + if (strlen(members[x].membername) > 0 && strcmp(members[x].membername, members[i].membername) != 0) { + SetRaidLeader(members[i].membername, members[x].membername); + UpdateRaidAAs(); + SendAllRaidLeadershipAA(); + break; + } + } + } + } +} \ No newline at end of file diff --git a/zone/raids.h b/zone/raids.h index 885dd2fa1..52bbc3b3a 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -88,6 +88,8 @@ struct RaidMember{ bool IsGroupLeader; bool IsRaidLeader; bool IsLooter; + bool IsBot = false; + bool IsRaidMainAssistOne = false; }; struct GroupMentor { @@ -113,6 +115,11 @@ public: bool IsRaid() { return true; } void AddMember(Client *c, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); + void AddBot(Bot* b, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); + void RaidGroupSay(const char* msg, const char* from, uint8 language, uint8 lang_skill); + void RaidSay(const char* msg, const char* from, uint8 language, uint8 lang_skill); + bool IsEngaged(); + Mob* GetRaidMainAssistOneByName(const char* name); void RemoveMember(const char *c); void DisbandRaid(); void MoveMember(const char *name, uint32 newGroup); @@ -124,6 +131,7 @@ public: bool IsRaidMember(const char* name); bool IsRaidMember(Client *c); void UpdateLevel(const char *name, int newLevel); + void SetNewRaidLeader(uint32 i); uint32 GetFreeGroup(); uint8 GroupCount(uint32 gid); @@ -244,6 +252,10 @@ public: bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0); std::vector GetMembers() const; + std::vector GetRaidGroupMembers(uint32 gid); + std::vector GetRaidGroupBotMembers(uint32 gid); + std::vector GetRaidBotMembers(uint32 owner = 0); + void HandleBotGroupDisband(uint32 owner, uint32 gid = RAID_GROUPLESS); RaidMember members[MAX_RAID_MEMBERS]; char leadername[64]; diff --git a/zone/spells.cpp b/zone/spells.cpp index fd382ff95..c8fdf9f08 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -430,26 +430,16 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // If you're at full mana, let it cast even if you dont have enough mana // we calculated this above, now enforce it - if (mana_cost > 0 && slot != CastingSlot::Item) { + if (mana_cost > 0 && slot != CastingSlot::Item || (IsBot() && !CastToBot()->IsBotNonSpellFighter())) { int my_curmana = GetMana(); int my_maxmana = GetMaxMana(); if (my_curmana < mana_cost) {// not enough mana //this is a special case for NPCs with no mana... - if (IsNPC() && my_curmana == my_maxmana){ + if (IsNPC() && my_curmana == my_maxmana) { mana_cost = 0; - } else { - //The client will prevent spell casting if insufficient mana, this is only for serverside enforcement. - LogSpells("Spell Error not enough mana spell=[{}] mymana=[{}] cost=[{}]\n", spell_id, my_curmana, mana_cost); - if (IsClient()) { - //clients produce messages... npcs should not for this case - MessageString(Chat::Red, INSUFFICIENT_MANA); - InterruptSpell(); - } else { - InterruptSpell(0, 0, 0); //the 0 args should cause no messages - } - ZeroCastingVars(); - return(false); } + DoSpellInterrupt(spell_id, mana_cost, my_curmana); + return false; } } @@ -475,7 +465,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // cast time is 0, just finish it right now and be done with it if(cast_time == 0) { CastedSpellFinished(spell_id, target_id, slot, mana_cost, item_slot, resist_adjust); // - return(true); + return true; } // ok we know it has a cast time so we can start the timer now @@ -501,7 +491,20 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } } - return(true); + return true; +} + +void Mob::DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana) { + //The client will prevent spell casting if insufficient mana, this is only for serverside enforcement. + LogSpells("Spell Error not enough mana spell=[{}] mymana=[{}] cost=[{}]\n", spell_id, my_curmana, mana_cost); + if (IsClient()) { + //clients produce messages... npcs should not for this case + MessageString(Chat::Red, INSUFFICIENT_MANA); + InterruptSpell(); + } else { + InterruptSpell(0, 0, 0); //the 0 args should cause no messages + } + ZeroCastingVars(); } void Mob::SendBeginCast(uint16 spell_id, uint32 casttime) @@ -4153,7 +4156,6 @@ bool Mob::SpellOnTarget( return false; } } - if (spelltar->IsClient()){ spelltar->CastToClient()->BreakSneakWhenCastOn(this, false); spelltar->CastToClient()->BreakFeignDeathWhenCastOn(false); diff --git a/zone/string_ids.h b/zone/string_ids.h index a8ab5a58b..cc3e1bad7 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -362,6 +362,7 @@ #define PETITION_NO_DELETE 5053 //You do not have a petition in the queue. #define PETITION_DELETED 5054 //Your petition was successfully deleted. #define ALREADY_IN_RAID 5060 //%1 is already in a raid. +#define ALREADY_IN_YOUR_RAID 5077 //%1 is already in your raid. #define GAIN_RAIDEXP 5085 //You gained raid experience! #define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there. #define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure. diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index b0425c4a4..5a5025ac4 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -1500,7 +1500,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (strcmp(rmsg->from, r->members[x].member->GetName()) != 0) { if (r->members[x].GroupNumber == rmsg->gid) { - if (r->members[x].member->GetFilter(FilterGroupChat) != 0) + if (!r->members[x].IsBot && r->members[x].member->GetFilter(FilterGroupChat) != 0) { r->members[x].member->ChannelMessageSend(rmsg->from, r->members[x].member->GetName(), ChatChannel_Group, rmsg->language, rmsg->lang_skill, rmsg->message); } @@ -1524,7 +1524,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (r->members[x].member) { if (strcmp(rmsg->from, r->members[x].member->GetName()) != 0) { - if (r->members[x].member->GetFilter(FilterGroupChat) != 0) + if (!r->members[x].IsBot && r->members[x].member->GetFilter(FilterGroupChat) != 0) { r->members[x].member->ChannelMessageSend(rmsg->from, r->members[x].member->GetName(), ChatChannel_Raid, rmsg->language, rmsg->lang_skill, rmsg->message); }