diff --git a/.gitignore b/.gitignore index d835a1ceb..be8fccbc3 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,5 @@ bin/ /Win32 /x64 /client_files/**/CMakeFiles/ - -.idea +submodules/libuv +.idea \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 000000000..9204f06eb --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + } + ] +} \ No newline at end of file diff --git a/common/repositories/base/base_raid_members_repository.h b/common/repositories/base/base_raid_members_repository.h index 2dc350ae1..88169a5ab 100644 --- a/common/repositories/base/base_raid_members_repository.h +++ b/common/repositories/base/base_raid_members_repository.h @@ -27,6 +27,7 @@ public: int isgroupleader; int israidleader; int islooter; + int isbot; }; static std::string PrimaryKey() @@ -46,6 +47,7 @@ public: "isgroupleader", "israidleader", "islooter", + "isbot", }; } @@ -90,6 +92,7 @@ public: entry.isgroupleader = 0; entry.israidleader = 0; entry.islooter = 0; + entry.isbot = 0; return entry; } @@ -133,7 +136,8 @@ public: entry.name = row[5] ? row[5] : ""; entry.isgroupleader = atoi(row[6]); entry.israidleader = atoi(row[7]); - entry.islooter = atoi(row[8]); + entry.islooter = atoi(row[8]); + entry.isbot = atoi(row[9]); return entry; } @@ -176,6 +180,7 @@ public: update_values.push_back(columns[6] + " = " + std::to_string(raid_members_entry.isgroupleader)); update_values.push_back(columns[7] + " = " + std::to_string(raid_members_entry.israidleader)); update_values.push_back(columns[8] + " = " + std::to_string(raid_members_entry.islooter)); + update_values.push_back(columns[9] + " = " + std::to_string(raid_members_entry.isbot)); //Mitch auto results = db.QueryDatabase( fmt::format( @@ -206,6 +211,7 @@ public: insert_values.push_back(std::to_string(raid_members_entry.isgroupleader)); insert_values.push_back(std::to_string(raid_members_entry.israidleader)); insert_values.push_back(std::to_string(raid_members_entry.islooter)); + insert_values.push_back(std::to_string(raid_members_entry.isbot)); auto results = db.QueryDatabase( fmt::format( @@ -244,6 +250,7 @@ public: insert_values.push_back(std::to_string(raid_members_entry.isgroupleader)); insert_values.push_back(std::to_string(raid_members_entry.israidleader)); insert_values.push_back(std::to_string(raid_members_entry.islooter)); + insert_values.push_back(std::to_string(raid_members_entry.isbot)); insert_chunks.push_back("(" + implode(",", insert_values) + ")"); } @@ -286,6 +293,7 @@ public: entry.isgroupleader = atoi(row[6]); entry.israidleader = atoi(row[7]); entry.islooter = atoi(row[8]); + entry.isbot = atoi(row[9]); all_entries.push_back(entry); } @@ -319,6 +327,7 @@ public: entry.isgroupleader = atoi(row[6]); entry.israidleader = atoi(row[7]); entry.islooter = atoi(row[8]); + entry.isbot = atoi(row[9]); all_entries.push_back(entry); } diff --git a/common/ruletypes.h b/common/ruletypes.h index 37be4a438..33fa0d018 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -617,10 +617,13 @@ RULE_REAL(Bots, LeashDistance, 562500.0f, "Distance a bot is allowed to travel f RULE_BOOL(Bots, AllowApplyPoisonCommand, true, "Allows the use of the bot command 'applypoison'") RULE_BOOL(Bots, AllowApplyPotionCommand, true, "Allows the use of the bot command 'applypotion'") RULE_BOOL(Bots, RestrictApplyPotionToRogue, true, "Restricts the bot command 'applypotion' to rogue-usable potions (i.e., poisons)") +RULE_BOOL(Bots, DisplayHealDamage, false, "Enables the display of bot heal damage to the bot owner client") +RULE_BOOL(Bots, DisplaySpellDamage, false, "Enables the display of bot spell damage to the bot owner client") RULE_BOOL(Bots, OldRaceRezEffects, false, "Older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore") RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.") RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell") RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell") + RULE_CATEGORY_END() #endif diff --git a/common/types.h b/common/types.h index b3be97d32..2a6355296 100644 --- a/common/types.h +++ b/common/types.h @@ -57,6 +57,9 @@ typedef const char Const_char; //for perl XS #define safe_delete(d) if(d) { delete d; d=nullptr; } #define safe_delete_array(d) if(d) { delete[] d; d=nullptr; } +//#define safe_delete(d)(_RPTF0(_CRT_WARN,"Testing delete function\n")); +//#define safe_delete(d) if(d) { } +//#define safe_delete_array(d) if(d) { } #define L32(i) ((uint32) i) #define H32(i) ((uint32) (i >> 32)) #define L16(i) ((uint16) i) diff --git a/zone/attack.cpp b/zone/attack.cpp index a7a03127e..b031e4036 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -49,6 +49,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA extern QueryServ* QServ; extern WorldServer worldserver; extern FastMath g_Math; +extern bool Critical; #ifdef _WINDOWS #define snprintf _snprintf @@ -3582,7 +3583,7 @@ bool Mob::CheckDoubleAttack() return zone->random.Int(1, 500) <= chance; } -void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const EQ::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks special) { +void Mob::CommonDamage(Mob* attacker, int& damage, const uint16 spell_id, const EQ::skills::SkillType skill_used, bool& avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks special) { // This method is called with skill_used=ABJURE for Damage Shield damage. bool FromDamageShield = (skill_used == EQ::skills::SkillAbjuration); bool ignore_invul = false; @@ -3652,7 +3653,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const // and remove sitting regen. Removes bug where client clicks sit // during battle and gains pet hp-regen and bugs the sit button. if (IsPet()) { - Mob *owner = this->GetOwner(); + Mob* owner = this->GetOwner(); if (owner && owner->IsClient()) { if (GetPetOrder() == SPO_Sit) { SetPetOrder(SPO_Follow); @@ -3665,7 +3666,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const } //end `if there is some damage being done and theres anattacker person involved` - Mob *pet = GetPet(); + 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 @@ -3681,7 +3682,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap() - ) { + ) { if (!pet->IsHeld()) { LogAggro("Sending pet [{}] into battle due to attack", pet->GetName()); if (IsClient()) { @@ -3905,12 +3906,12 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const //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(); if (owner && owner->IsClient()) { - if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage>0) { + if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage > 0) { //special crap for spell damage, looks hackish to me char val1[20] = { 0 }; owner->MessageString(Chat::NonMelee, OTHER_HIT_NONMELEE, GetCleanName(), ConvertArray(damage, val1)); @@ -3931,6 +3932,33 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const owner->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); } } + +#ifdef BOTS + else if (owner && owner->IsBot() && RuleB(Bots, DisplaySpellDamage)) { + if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage > 0) { + //special crap for spell damage, looks hackish to me + char val1[20] = { 0 }; + owner->CastToBot()->GetBotOwner()->CastToClient()->MessageString(Chat::NonMelee, OTHER_HIT_NONMELEE, attacker->GetCleanName(), ConvertArray(damage, val1)); + + } + else { + if (damage > 0) { + if (spell_id != SPELL_UNKNOWN) + filter = iBuffTic ? FilterDOT : FilterSpellDamage; + else + filter = FilterPetHits; + } + else if (damage == -5) + filter = FilterNone; //cant filter invulnerable + else + filter = FilterPetMisses; + + if (!FromDamageShield) + owner->CastToBot()->GetBotOwner()->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); + + } + } +#endif skip = owner; } else { @@ -3969,8 +3997,10 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const filter = FilterNone; //cant filter invulnerable else filter = FilterMyMisses; - + //set to filter duplicate message to the client if client uses #damage to themselves + //if (!attacker->IsClient() && attacker->CastToClient()->GetID() != attacker->GetTarget()->GetID()) { attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter); + //} } } skip = attacker; @@ -3992,7 +4022,20 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const // If this is Damage Shield damage, the correct OP_Damage packets will be sent from Mob::DamageShield, so // we don't send them here. if (!FromDamageShield) { - +#ifdef BOTS + // If a bot is the attacker, send a damage message ot the Bot Owner + if (attacker->GetTarget() && spell_id != SPELL_UNKNOWN && damage > 0 && !Critical && attacker && attacker != this && attacker->IsBot() && RuleB(Bots, DisplaySpellDamage)) { + attacker->CastToBot()->GetBotOwner()->FilteredMessageString( + attacker->CastToBot()->GetBotOwner(), + Chat::DotDamage, + FilterDOT, + OTHER_HIT_DOT, + attacker->GetTarget()->GetCleanName(), + itoa(damage), + attacker->GetCleanName(), + spells[spell_id].name); + } +#endif entity_list.QueueCloseClients( this, /* Sender */ outapp, /* packet */ @@ -4001,10 +4044,10 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const skip, /* Skip this mob */ true, /* Packet ACK */ filter /* eqFilterType filter */ - ); + ); //send the damage to ourself if we are a client - if (IsClient()) { + if (IsClient() && this != attacker) { //need to add a filter to remove duplicate display for #damage to self //I dont think any filters apply to damage affecting us CastToClient()->QueuePacket(outapp); } @@ -4034,11 +4077,25 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const spells[spell_id].name /* Message4 */ ); } - } //end packet sending +#ifdef BOTS + // If a bot is the attacker, send a damage message ot the Bot Owner + else if (attacker->GetTarget() && spell_id != SPELL_UNKNOWN && attacker->IsBot() && damage > 0 && !Critical && attacker && attacker != this && RuleB(Bots, DisplaySpellDamage)) { + attacker->CastToBot()->GetBotOwner()->FilteredMessageString( + attacker->CastToBot()->GetBotOwner(), + Chat::DotDamage, + FilterDOT, + OTHER_HIT_DOT, + attacker->GetTarget()->GetCleanName(), + itoa(damage), + attacker->GetCleanName(), + spells[spell_id].name); + } +#endif + } -} +} //end packet sending -void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) +void Mob::HealDamage(uint32 amount, Mob* caster, uint16 spell_id) { int32 maxhp = GetMaxHP(); int32 curhp = GetHP(); @@ -4053,15 +4110,16 @@ void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) if (caster) { if (IsBuffSpell(spell_id)) { // hots // message to caster - if (caster->IsClient() && caster == this) { - if (caster->CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) + 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), @@ -4070,8 +4128,35 @@ void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) caster->FilteredMessageString(caster, Chat::NonMelee, FilterHealOverTime, YOU_HEAL, GetCleanName(), itoa(acthealed)); } +#ifdef BOTS + if (caster->IsBot() && this != caster->CastToBot()->GetBotOwner() && RuleB(Bots, DisplayHealDamage)) { + + + // %1 healed %2 for %3 hit points from %4's %5 + const char s2[]{ " healed " }; + const char s4[]{ " for " }; + const char s6[]{ " hit points from " }; + const char s8[]{ "'s " }; + + caster->CastToBot()->GetBotOwner()->FilteredMessageString( + caster->CastToBot()->GetBotOwner(), //send to the Bot Owner's client + Chat::NonMelee, + FilterHealOverTime, + GENERIC_9_STRINGS, //using generic for testing purposes %1 %2 %3 %4 %5 %6 %7 %8 %9 + caster->GetCleanName(), // %1 caster (bot's) name + s2, // %2 + this->GetCleanName(), // %3 caster (bot's) target + s4, // %4 + itoa(acthealed), // %5 amount healed + s6, // %6 + caster->GetCleanName(), // %7 caster (bot's) name + s8, // %8 + spells[spell_id].name // %9 spell name + ); + } +#endif // BOTS // message to target - if (IsClient() && caster != this) { + if ((IsClient() && caster != this)) { if (CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater) FilteredMessageString(this, Chat::NonMelee, FilterHealOverTime, HOT_HEALED_OTHER, caster->GetCleanName(), @@ -4084,25 +4169,41 @@ void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) else { // normal heals FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage, YOU_HEALED, caster->GetCleanName(), itoa(acthealed)); +#ifndef BOTS if (caster != this) caster->FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage, YOU_HEAL, GetCleanName(), itoa(acthealed)); } - } - else { - Message(Chat::NonMelee, "You have been healed for %d points of damage.", acthealed); - } - } +#endif +#ifdef BOTS + if (caster->IsBot() && RuleB(Bots, DisplayHealDamage)) { + caster->CastToBot()->GetBotOwner()->FilteredMessageString(caster->CastToBot()->GetBotOwner(), + Chat::NonMelee, FilterSpellDamage, GENERIC_9_STRINGS, + caster->GetCleanName(), " healed ", this->GetCleanName(), " for ", itoa(acthealed), " hit points.", " ", " ", " "); + } + else if (caster != this) { + caster->FilteredMessageString(caster, Chat::NonMelee, FilterSpellDamage, + YOU_HEAL, GetCleanName(), itoa(acthealed)); - if (curhp < maxhp) { - if ((curhp + amount) > maxhp) - curhp = maxhp; - else - curhp += amount; - SetHP(curhp); + } + } +#endif // BOTS - SendHPUpdate(); } + else { + Message(Chat::NonMelee, "You have been healed for %d points of damage.", acthealed); + } +} + +if (curhp < maxhp) { + if ((curhp + amount) > maxhp) + curhp = maxhp; + else + curhp += amount; + SetHP(curhp); + + SendHPUpdate(); +} } //proc chance includes proc bonus diff --git a/zone/bot.cpp b/zone/bot.cpp index dcb8af574..a7c9bd6f5 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -20,6 +20,7 @@ #include "bot.h" #include "object.h" +#include "raids.h" #include "doors.h" #include "quest_parser_collection.h" #include "lua_parser.h" @@ -27,6 +28,7 @@ #include "../common/say_link.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) { @@ -2123,7 +2125,11 @@ bool Bot::Process() } // Bot AI - AI_Process(); + Raid* bot_raid = entity_list.GetRaidByBotName(this->GetName()); + if (bot_raid) + AI_Process_Raid(); + else + AI_Process(); return true; } @@ -2362,11 +2368,11 @@ void Bot::AI_Process() Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); Group* bot_group = GetGroup(); - + //#pragma region PRIMARY AI SKIP CHECKS // Primary reasons for not processing AI - if (!bot_owner || !bot_group || !IsAIControlled()) { + if (!bot_owner || (!bot_group) || !IsAIControlled()) { return; } @@ -2379,7 +2385,12 @@ void Bot::AI_Process() } // We also need a leash owner and follow mob (subset of primary AI criteria) - Client* leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); + Client* leash_owner = nullptr; + + if (bot_group) { + leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); + } + if (!leash_owner) { return; } @@ -3810,7 +3821,7 @@ void Bot::Depop() { entity_list.RemoveFromHateLists(this); if(HasGroup()) Bot::RemoveBotFromGroup(this, GetGroup()); - + if(HasPet()) GetPet()->Depop(); @@ -3865,7 +3876,13 @@ bool Bot::Spawn(Client* botCharacterOwner) { this->SendWearChange(materialFromSlot); } } + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + if (raid) + { + raid->VerifyRaid(); + this->SetRaidGrouped(true); + } return true; } @@ -4727,7 +4744,24 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQ::skills::Skill my_owner->CastToClient()->SetBotPulling(false); } + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + + if (raid) + { + + for (int x = 0; x < MAX_RAID_MEMBERS; x++) + { + if (strcmp(raid->members[x].membername, this->GetName()) == 0) + { + raid->members[x].member = nullptr; + } + } + } +// else +// { entity_list.RemoveBot(this->GetID()); +// } + return true; } @@ -6692,7 +6726,7 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (spells[spell_id].target_type == ST_Self) return value; - bool Critical = false; + Critical = false; int32 value_BaseEffect = 0; value_BaseEffect = (value + (value*GetBotFocusEffect(focusFcBaseEffects, spell_id) / 100)); // Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40. @@ -6737,8 +6771,9 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) value += (GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value) * ratio / 100); - - entity_list.MessageClose(this, false, 100, Chat::SpellCrit, "%s delivers a critical blast! (%d)", GetName(), -value); + //Mitch + //if (!RuleB(Bots, DisplaySpellDamage)) + entity_list.MessageClose(this, false, 100, Chat::SpellCrit, "%s\'s %s delivers a critical blast to %s! (%d)", GetName(), spells[spell_id].name, target->GetCleanName(), -value); return value; } @@ -6769,7 +6804,7 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { int32 value_BaseEffect = 0; int32 chance = 0; int8 modifier = 1; - bool Critical = false; + Critical = false; //mitch value_BaseEffect = (value + (value*GetBotFocusEffect(focusFcBaseEffects, spell_id) / 100)); value = value_BaseEffect; value += int(value_BaseEffect*GetBotFocusEffect(focusImprovedHeal, spell_id) / 100); @@ -7411,11 +7446,27 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, EQ::spells::CastingSlot slot, bool& stopLogic) { bool isMainGroupMGB = false; + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + if(isMainGroupMGB && (GetClass() != BARD)) { BotGroupSay(this, "MGB %s", spells[spell_id].name); SpellOnTarget(spell_id, this); entity_list.AESpell(this, this, spell_id, true); - } else { + } + else if (raid) + { + //for (auto& iter : raid->GetRaidGroupMembers(raid->GetGroup(this->GetName()))) { + std::vector raid_group_members = raid->GetRaidGroupMembers(raid->GetGroup(this->GetName())); + for (std::vector::iterator 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) { @@ -8084,6 +8135,16 @@ void Bot::Camp(bool databaseSave) { //auto group = GetGroup(); if(GetGroup()) RemoveBotFromGroup(this, GetGroup()); + + //Mitch + Raid* bot_raid = entity_list.GetRaidByBotName(this->GetName()); + if (bot_raid) { + uint32 gid = bot_raid->GetGroup(this->GetName()); + bot_raid->SendRaidGroupRemove(this->GetName(), bot_raid->GetGroup(this->GetName())); + bot_raid->RemoveMember(this->GetName()); + bot_raid->GroupUpdate(gid); + } + // RemoveBotFromGroup() code is too complicated for this to work as-is (still needs to be addressed to prevent memory leaks) //if (group->GroupCount() < 2) @@ -8098,9 +8159,14 @@ void Bot::Camp(bool databaseSave) { } void Bot::Zone() { - if(HasGroup()) + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + if (raid) { + raid->MemberZoned(this->CastToClient()); + } + else if (HasGroup()) { GetGroup()->MemberZoned(this); - + } + Save(); Depop(); } @@ -8304,8 +8370,9 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { void Bot::ProcessBotGroupInvite(Client* c, std::string botName) { if(c) { +// Bot* invitedBot = GetBotByBotClientOwnerAndBotName(entity_list.GetBotByBotName(botName)->GetOwner()->CastToClient(), botName); Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); - + //Mitch changed entity from c if(invitedBot && !invitedBot->HasGroup()) { if(!c->IsGrouped()) { Group *g = new Group(c); @@ -8349,7 +8416,11 @@ void Bot::ProcessClientZoneChange(Client* botOwner) { Bot* tempBot = *itr; if(tempBot) { - if(tempBot->HasGroup()) { + 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()) { @@ -8900,11 +8971,51 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } +#ifdef BOTS + else if (caster->IsRaidGrouped()) + { + //added raid check + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 gid = raid->GetGroup(caster->GetName()); + if (gid < 12) { + 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; + } + } + } + } +#endif } if( botCasterClass == PALADIN || botCasterClass == BEASTLORD || botCasterClass == RANGER) { - if(caster->HasGroup()) { - Group *g = caster->GetGroup(); + if(caster->HasGroup() || caster->IsRaidGrouped()) { float hpRatioToHeal = 25.0f; switch(caster->GetBotStance()) { case EQ::constants::stanceReactive: @@ -8921,6 +9032,11 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl hpRatioToHeal = 25.0f; break; } + Group* g = caster->GetGroup(); + uint32 gid = 0xff; + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + if (raid) + uint32 gid = raid->GetGroup(caster->GetName()); if(g) { for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { @@ -8949,6 +9065,39 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } + else if (gid < 12) + { + 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; + } + } + } } } } @@ -8961,7 +9110,22 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl else return false; } - +#ifdef BOTS +//added raid check + if (caster->IsRaidGrouped()) { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 g = raid->GetGroup(caster->GetName()); + if (g < 12) { + 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; + } + } + } + } +#endif if(caster->HasGroup()) { Group *g = caster->GetGroup(); if(g) { @@ -8994,6 +9158,27 @@ 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 < 12) { + 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; + } + } + } + } } if (iSpellTypes == SpellType_HateRedux) { @@ -9011,13 +9196,41 @@ 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 < 12) { + 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()) { + //added raid check + if (caster->IsRaidGrouped()) { + Raid* raid = entity_list.GetRaidByBotName(caster->GetName()); + uint32 g = raid->GetGroup(caster->GetName()); + if (g < 12) { + 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, iChance, SpellType_PreCombatBuff) || caster->AICastSpell(iter->member->GetPet(), iChance, SpellType_PreCombatBuff)) + return true; + } + } + } + } else if (caster->HasGroup()) { Group *g = caster->GetGroup(); if (g) { for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { @@ -9808,4 +10021,543 @@ void Bot::StopMoving(float new_heading) uint8 Bot::spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND] = { 0 }; +void Bot::ProcessRaidInvite(Client* invitee, Client* invitor) { + + if (!invitee || !invitor) + return; + + Raid* raid = entity_list.GetRaidByClient(invitor); + Group* g_invitee = invitee->GetGroup(); + Group* g_invitor = invitor->GetGroup(); + + if (raid) + { + if (g_invitee) + { + //As there is already a raid, just add this group + raid->SendBulkRaid(invitee); + raid->SendMakeLeaderPacketTo(raid->leadername, invitee); //added to resolve Basic's raid window not showing a raid leader + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + raid->GroupUpdate(raid_free_group_id); +// raid->SendBulkRaid(invitor); //Send a raid updates to the invitor + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //As there is already a raid and no group, just add this single client + raid->SendRaidCreate(invitee); + raid->AddMember(invitee); + raid->SendMakeLeaderPacketTo(raid->leadername, invitee); //moved to be after the addmember to resolve raid window not showing a raid leader + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitee); + } + } + } + else + { + //As there is no raid + //First, create the raid + raid = new Raid(invitor); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); + raid->SendRaidCreate(invitor); + raid->SendMakeLeaderPacketTo(raid->leadername, invitor); //added to resolve no raid leader shown in Rola's raid window + + if (g_invitor) + { + //Second, add the invitor group as group 0 + for (int x = 0; x < 6; x++) { + if (g_invitor->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitor->members[x] && g_invitor->members[x]->IsBot()) { + b = g_invitor->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, 0, false, true, false); + raid->SetGroupLeader(b->GetName()); + raid->GroupUpdate(0); + } + else { + raid->AddBot(b, 0, false, false, false); + raid->GroupUpdate(0); + } + } + else if (g_invitor->members[x] && g_invitor->members[x]->IsClient()) { + c = g_invitor->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, 0, false, true, false); + raid->SetGroupLeader(c->GetName()); + raid->GroupUpdate(0); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, 0, false, false, false); + raid->GroupUpdate(0); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + raid->GroupUpdate(0); + raid->SendBulkRaid(invitee); //Send a raid updates to the invitor + g_invitor->JoinRaidXTarget(raid, true); + g_invitor->DisbandGroup(true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + if (g_invitee) + { + //Third, add the invitee group + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + raid->GroupUpdate(raid_free_group_id); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + raid->GroupUpdate(raid_free_group_id); +// raid->SendBulkRaid(invitee); //Send a raid updates to the invitee + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //Third, no group so add the single client + raid->SendRaidCreate(invitee); + raid->SendMakeLeaderPacketTo(raid->leadername, invitee); + raid->AddMember(invitee); +// raid->SendBulkRaid(invitee); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitee); + } + } + } + else + { + //Second, add the single invitor + raid->SendRaidCreate(invitor); + raid->SendMakeLeaderPacketTo(raid->leadername, invitor); + raid->AddMember(invitor, 0xFFFFFFFF, true, false, true); + raid->SendBulkRaid(invitee); //Send a raid updates to the invitee + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + if (g_invitee) + { + //Third, add the invitee group + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + raid->GroupUpdate(raid_free_group_id); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + raid->GroupUpdate(raid_free_group_id); + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //Third, no group so add the single client invitee + raid->SendRaidCreate(invitee); + raid->SendMakeLeaderPacketTo(raid->leadername, invitee); + raid->AddMember(invitee); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitee); + } + } + } + } +} + +void Bot::ProcessRaidInvite(Bot* invitee, Client* invitor) { + + if (!invitee || !invitor) + return; + + Raid* raid = entity_list.GetRaidByClient(invitor); + Group* g_invitee = invitee->GetGroup(); + Group* g_invitor = invitor->GetGroup(); + + if (raid) + { + if (g_invitee) + { + //As there is already a raid, just add this group + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + raid->GroupUpdate(raid_free_group_id); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + // raid->SendBulkRaid(invitor); //Send a raid updates to the invitor + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //As there is already a raid and no group, just add this single client + //raid->SendRaidCreate(invitee); + //raid->SendMakeLeaderPacketTo(raid->leadername, invitee); + raid->AddBot(invitee); + //if (raid->IsLocked()) { + // raid->SendRaidLockTo(invitee); + //} + } + } + else + { + //As there is no raid + //First, create the raid + raid = new Raid(invitor); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); +// raid->SendRaidCreate(invitor); +// raid->SetLeader(invitor); //Added Jan 18 +// raid->SendMakeLeaderPacketTo(raid->leadername, invitor); + + if (g_invitor) + { + //Second, add the invitor group as group 0 + for (int x = 0; x < 6; x++) { + if (g_invitor->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitor->members[x] && g_invitor->members[x]->IsBot()) { + b = g_invitor->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, 0, false, true, false); + raid->SetGroupLeader(b->GetName()); + //raid->GroupUpdate(0); + } + else { + raid->AddBot(b, 0, false, false, false); + //raid->GroupUpdate(0); + b->SetFollowID(g_invitor->GetLeader()->GetID()); + } + } + else if (g_invitor->members[x] && g_invitor->members[x]->IsClient()) { + c = g_invitor->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->AddMember(c, 0, true, true, true); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + //raid->SetGroupLeader(c->GetName()); //Mitch Jan 18 + //raid->GroupUpdate(0, true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, 0, false, false, false); + //raid->GroupUpdate(0, true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } +// raid->GroupUpdate(0, true); +// raid->SendBulkRaid(invitee); //Send a raid updates to the invitor + g_invitor->JoinRaidXTarget(raid, true); + g_invitor->DisbandGroup(true); //Added Jan 23 to fix group database and entity integrity + raid->GroupUpdate(0, true); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + if (g_invitee) + { + //Third, add the invitee group + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + //raid->SendBulkRaid(invitor); //Send a raid updates to the invitor + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //Third, no group so add the single client + //raid->SendRaidCreate(invitee); + //raid->SendMakeLeaderPacketTo(raid->leadername, invitee); + raid->AddBot(invitee); + // raid->SendBulkRaid(invitee); + //if (raid->IsLocked()) { + // raid->SendRaidLockTo(invitee); + //} + } + } + else + { + //Second, add the single invitor + raid->SendRaidCreate(invitor); + raid->AddMember(invitor, 0xFFFFFFFF, true, false, true); + raid->SendMakeLeaderPacketTo(invitor->GetName(), invitor); //Mitch Jan 18 + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + if (g_invitee) + { + //Third, add the invitee group + uint32 raid_free_group_id = raid->GetFreeGroup(); + for (int x = 0; x < 6; x++) { + if (g_invitee->members[x]) { + Client* c = nullptr; + Bot* b = nullptr; + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) { + b = g_invitee->members[x]->CastToBot(); + if (x == 0) { + raid->AddBot(b, raid_free_group_id, false, true, false); + raid->SetGroupLeader(b->GetName()); + } + else { + raid->AddBot(b, raid_free_group_id, false, false, false); + } + } + else if (g_invitee->members[x] && g_invitee->members[x]->IsClient()) { + c = g_invitee->members[x]->CastToClient(); + if (x == 0) { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true, false); + raid->SetGroupLeader(c->GetName()); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + else { + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, false, false); + if (raid->IsLocked()) { + raid->SendRaidLockTo(c); + } + } + } + } + } + // raid->SendBulkRaid(invitor); //Send a raid updates to the invitor + g_invitee->JoinRaidXTarget(raid, true); + g_invitee->DisbandGroup(true); + raid->GroupUpdate(raid_free_group_id); + if (raid->IsLocked()) { + raid->SendRaidLockTo(invitor); + } + } + else + { + //Third, no group so add the single client invitee + //raid->SendRaidCreate(invitee); + //raid->SendMakeLeaderPacketTo(raid->leadername, invitee); + raid->AddBot(invitee); + invitee->SetFollowID(invitor->GetID()); + //if (raid->IsLocked()) { + // raid->SendRaidLockTo(invitee); + //} + } + } + } +} + #endif diff --git a/zone/bot.h b/zone/bot.h index 5ed980fe8..6cd8eb519 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -35,6 +35,7 @@ #include "../common/global_define.h" #include "guild_mgr.h" #include "worldserver.h" +#include "raids.h" #include @@ -151,8 +152,8 @@ public: ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } - virtual Raid* GetRaid() { return entity_list.GetRaidByMob(this); } - virtual Group* GetGroup() { return entity_list.GetGroupByMob(this); } + virtual Raid* GetRaid() { return entity_list.GetRaidByMob(this); } // GetRaidByMob(this); + virtual Group* GetGroup() { return entity_list.GetGroupByMob(this); } // GetGroupByMob; // Common, but informal "interfaces" with Client object uint32 CharacterID() { return GetBotID(); } // Just returns the Bot Id @@ -377,6 +378,15 @@ public: static bool CheckDisciplineRecastTimers(Bot *caster, int timer_index); static uint32 GetDisciplineRemainingTime(Bot *caster, int timer_index); + //Raid methods + void PetAIProcess_Raid(); + void AI_Process_Raid(); + bool AICastSpell_Raid(Mob* tar, uint8 iChance, uint32 iSpellTypes); + static void ProcessRaidInvite(Bot* invitee, Client* invitor); //Mitch + static void ProcessRaidInvite(Client* invitee, Client* invitor); //Mitch + uint8 GetNumberNeedingHealedInRaidGroup(uint8 hpr, bool includePets); //Mitch + inline void SetDirtyAutoHaters() { m_dirtyautohaters = true; } //Mitch + 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); @@ -597,6 +607,9 @@ public: int32 GetBaseDR() { return _baseDR; } int32 GetBaseCorrup() { return _baseCorrup; } + //Raid additions + Raid* p_raid_instance; + protected: virtual void PetAIProcess(); virtual void BotMeditate(bool isSitting); @@ -661,6 +674,7 @@ private: 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; @@ -727,6 +741,7 @@ private: public: static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND]; + }; #endif // BOTS diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 4b8d602a6..5427b3397 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2632,19 +2632,20 @@ bool BotDatabase::LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 g { if (!group_id || !owner_id) return false; - + //modified query for usecase of a BOTID sharing a CHARACTERID. + //in this usecase, when the BOTID=CHARACTERID of BOT OWNER, this BOTID would be added to the GROUP query = StringFormat( - "SELECT `charid`" - " FROM `group_id`" - " WHERE `groupid` = '%u'" - " AND `charid` IN (" - " SELECT `bot_id`" - " FROM `bot_data`" - " WHERE `owner_id` = '%u'" - " )", + "SELECT `mob_id` FROM `vw_groups` " + "WHERE `group_id` = '%u' " + "AND `mob_type`='B' " + "AND `mob_id` IN (SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u');" + , group_id, owner_id); + /*query = StringFormat( + "SELECT `charid` FROM `group_id` WHERE `groupid` = '%u'" + " AND `charid` IN (SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u')" + " AND `group_id.name` NOT IN (SELECT `name` FROM `character_data`)", group_id, - owner_id - ); + owner_id);*/ auto results = database.QueryDatabase(query); if (!results.Success()) diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp new file mode 100644 index 000000000..38edc4ac8 --- /dev/null +++ b/zone/bot_raid.cpp @@ -0,0 +1,2626 @@ +/* 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 +*/ + +#ifdef BOTS + +#include "bot.h" +#include "object.h" +#include "raids.h" +#include "doors.h" +#include "quest_parser_collection.h" +#include "lua_parser.h" +#include "../common/string_util.h" +#include "../common/say_link.h" + +extern volatile bool is_zone_loaded; +extern bool Critical; + +// 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 +}; + +void Bot::AI_Process_Raid() +{ +#define TEST_COMBATANTS() if (!GetTarget() || GetAppearance() == eaDead) { return; } +#define PULLING_BOT (GetPullingFlag() || GetReturningFlag()) +#define NOT_PULLING_BOT (!GetPullingFlag() && !GetReturningFlag()) +#define GUARDING (GetGuardFlag()) +#define NOT_GUARDING (!GetGuardFlag()) +#define HOLDING (GetHoldFlag()) +#define NOT_HOLDING (!GetHoldFlag()) +#define PASSIVE (GetBotStance() == EQ::constants::stancePassive) +#define NOT_PASSIVE (GetBotStance() != EQ::constants::stancePassive) + + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); + uint32 r_group = raid->GetGroup(GetName()); + + LogAI("Bot_Raid: Entered Raid Process() for [{}].", this->GetCleanName()); + + //#pragma region PRIMARY AI SKIP CHECKS + + // Primary reasons for not processing AI + if (!bot_owner || (!raid) || !IsAIControlled()) { + return; + } + + if (bot_owner->IsDead()) { + + SetTarget(nullptr); + SetBotOwner(nullptr); + + return; + } + + // We also need a leash owner and follow mob (subset of primary AI criteria) + Client* leash_owner = nullptr; + if (r_group < 12 && raid->IsGroupLeader(this->GetName())) { + leash_owner = raid->GetLeader(); + } + else if (r_group < 12) { + leash_owner = raid->GetGroupLeader(r_group); + } + else { + leash_owner = bot_owner; + } + + if (!leash_owner) { + return; + } + + //#pragma endregion + + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + if (!follow_mob) { + + follow_mob = leash_owner; + SetFollowID(leash_owner->GetID()); + } + + if (this->mana_timer.Check(false)) { + raid->SendHPManaEndPacketsFrom(this); + } + if (send_hp_update_timer.Check(false)) { + + raid->SendHPManaEndPacketsFrom(this); + + } + // 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 + + // 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); + } + + 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 + + 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(); + return; + } + + //#pragma region 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 + + 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); + } + } + } + } + + //#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) { + + Bot::BotGroupSay(this, "Pulling %s to the group..", pull_target->GetCleanName()); + //raid->RaidBotGroupSay(this, 0, 100, "Pulling %s to the group..", pull_target->GetCleanName()); + 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); + } + } + } + } + + //#pragma endregion + + //#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 { + + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + + Mob* bg_member = iter->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 + + glm::vec3 Goal(0, 0, 0); + + // We have aggro to choose from + if (IsEngaged()) { + + if (rest_timer.Enabled()) { + rest_timer.Disable(); + } + + //#pragma region 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); + } + + 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 + + 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(); + + return; + } + } + + //#pragma endregion + + //#pragma region ALT COMBAT (ACQUIRE TARGET) + + else if (bo_alt_combat && m_alt_combat_hate_timer.Check()) { // Find a mob from hate list to target + + // Raid Group roles can be expounded upon in the future + //r_group is the uint32 group id + auto assist_mob = raid->GetRaidMainAssistOneByName(this->GetName()); + bool find_target = true; + + if (!assist_mob) { + bot_owner->Message(Chat::Yellow, "Assist Mob is nullptr"); + } + + if (assist_mob) { + + if (assist_mob->GetTarget()) { + + if (assist_mob != this) { + + 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) { + + 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()) { + SetTarget(hate_list.GetClosestEntOnHateList(this, true)); + } + else { + + // This will keep bots on target for now..but, future updates will allow for rooting/stunning + SetTarget(hate_list.GetEscapingEntOnHateList(leash_owner, leash_distance)); + if (!GetTarget()) { + SetTarget(hate_list.GetEntWithMostHateOnList(this, nullptr, true)); + } + } + } + } + + //#pragma endregion + + //#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) { + + 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; + } + + //#pragma endregion + + //#pragma region 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(); + } + + 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)) { + + // We don't add to hate list here because it's assumed to already be on the list + GetPet()->SetTarget(tar); + } + + if (DivineAura()) { + return; + } + + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) { + SendAddPlayerState(PlayerState::Aggressive); + } + + //#pragma region PULLING FLAG (ACTIONABLE RANGE) + + if (GetPullingFlag()) { + + constexpr size_t PULL_AGGRO = 5225; // spells[5225]: 'Throw Stone' - 0 cast time + + if (tar_distance <= (spells[PULL_AGGRO].range * spells[PULL_AGGRO].range)) { + + StopMoving(); + CastSpell(PULL_AGGRO, tar->GetID()); + return; + } + } + + //#pragma endregion + + //#pragma region COMBAT RANGE CALCS + + 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; + + // Calculate caster distances + float caster_distance_max = 0.0f; + float caster_distance_min = 0.0f; + float caster_distance = 0.0f; + { + if (GetLevel() >= GetStopMeleeLevel() && GetClass() >= WARRIOR && GetClass() <= BERSERKER) { + caster_distance_max = MAX_CASTER_DISTANCE[(GetClass() - 1)]; + } + + if (caster_distance_max) { + + caster_distance_min = melee_distance_max; + if (caster_distance_max <= caster_distance_min) { + caster_distance_max = caster_distance_min * 1.25f; + } + + caster_distance = ((caster_distance_max + caster_distance_min) / 2); + } + } + + bool atArcheryRange = IsArcheryRange(tar); + + if (GetRangerAutoWeaponSelect()) { + + bool changeWeapons = false; + + if (atArcheryRange && !IsBotArcher()) { + + SetBotArcher(true); + changeWeapons = true; + } + else if (!atArcheryRange && IsBotArcher()) { + + SetBotArcher(false); + changeWeapons = true; + } + + if (changeWeapons) { + ChangeBotArcherWeapons(IsBotArcher()); + } + } + + if (IsBotArcher() && atArcheryRange) { + atCombatRange = true; + } + else if (caster_distance_max && tar_distance <= caster_distance_max) { + atCombatRange = true; + } + else if (tar_distance <= melee_distance) { + atCombatRange = true; + } + + //#pragma endregion + + //#pragma region 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())); + //} + + 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; + // } + //} + } + else { + + if (!IsSitting() && !IsFacingMob(tar)) { + + FaceTarget(tar); + return; + } + } + } + + if (!IsBotNonSpellFighter() && AI_EngagedCastCheck()) { + return; + } + + // 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 + // 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); + } + } + 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())) { + 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(); + TryWeaponProc(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(); + int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance); + if (ExtraAttackChanceBonus) { + + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + + if (zone->random.Int(0, 100) < ExtraAttackChanceBonus) { + Attack(tar, EQ::invslot::slotPrimary, false); + } + } + } + } + + 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(); + TryWeaponProc(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 (GetAppearance() == eaDead) { + return; + } + } + + //#pragma endregion + + //#pragma region ENGAGED NOT AT COMBAT RANGE + + 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()) { + + LogAI("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); + } + + return; + } + } + else { // Out-of-combat behavior + + SetAttackFlag(false); + SetAttackingFlag(false); + if (!bot_owner->GetBotPulling()) { + + SetPullingFlag(false); + SetReturningFlag(false); + } + + //#pragma region 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; + } + } + } + } + } + } + + //#pragma endregion + + SetTarget(nullptr); + + if (HasPet() && (GetClass() != ENCHANTER || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) { + + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + + if (m_PlayerState & static_cast(PlayerState::Aggressive)) { + SendRemovePlayerState(PlayerState::Aggressive); + } + + //#pragma region 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 if Bot is a member of a raid +void Bot::PetAIProcess_Raid() { + if (!HasPet() || !GetPet() || !GetPet()->IsNPC()) + return; + + Mob* BotOwner = this->GetBotOwner(); + NPC* botPet = this->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()); + 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.. + + // 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 (!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()) { + LogAI("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(); + } + } + } + } + } + } + else { + // Ok if we're not engaged, what's happening.. + if (botPet->GetTarget() != botPet->GetOwner()) + botPet->SetTarget(botPet->GetOwner()); + + if (!IsMoving()) + botPet->AI_IdleCastCheck(); + + 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; + } + case SPO_Sit: + botPet->SetAppearance(eaSitting); + break; + case SPO_Guard: + botPet->NextGuardPosition(); + break; + } + } + } +} + +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; +} + +bool Bot::AICastSpell_Raid(Mob* tar, uint8 iChance, uint32 iSpellTypes) { + + // Bot AI Raid + + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + uint32 r_group = raid->GetGroup(GetName()); + if (!raid) + return false; + + if (!tar) { + return false; + } + + if (!AI_HasSpells()) + return false; + + if (iChance < 100) { + if (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; + } + } + + uint8 botClass = GetClass(); + uint8 botLevel = GetLevel(); + + 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; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); + + if (castedSpell) + BotGroupSay(this, "Attempting to mez %s.", addMob->GetCleanName()); + //raid->RaidBotGroupSay(this, 0, 100, "Attempting to mez %s.", addMob->GetCleanName()); + } + break; + } + case SpellType_Heal: { + if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { + uint8 hpr = (uint8)tar->GetHPRatio(); + bool hasAggro = false; + bool isPrimaryHealer = false; + + if (this->IsRaidGrouped()) { + 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 (GetNumberNeedingHealedInRaidGroup(60, false) >= 3) + botSpell = GetBestBotSpellForGroupHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForPercentageHeal(this); + } + else if (hpr >= 70 && hpr < 95) { + if (GetNumberNeedingHealedInRaidGroup(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 (GetNumberNeedingHealedInRaidGroup(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 (GetNumberNeedingHealedInRaidGroup(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 (this->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].targettype == ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) + && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); + + 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 (IsRaidGrouped()) { + uint32 r_group = raid->GetGroup(GetName()); + BotGroupSay(this, "Casting %s.", spells[botSpell.SpellId].name); + //raid->RaidBotGroupSay(this, 0, 100, "Casting %s.", spells[botSpell.SpellId].name); + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + //for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + for (int i = 0; i < raid_group_members.size(); ++i){ + if (raid_group_members.at(i).member && !raid_group_members.at(i).member->qglobal) { + raid_group_members.at(i).member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); + } + } + } + } + else { + if (tar != this) //we don't need spam of bots healing themselves + BotGroupSay(this, "Casting %s on %s", spells[botSpell.SpellId].name, tar->GetCleanName()); + 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(); + + 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].targettype == ST_Pet) && (tar != this->GetPet())) + continue; + + // Validate target + + if (!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == 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 (this->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 (this->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 + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Nuke: { + if ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER))) + { + 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 (this->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 (this->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; + } + } + + 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) { + 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: { + + if (botClass == SHAMAN) { + checked_los = true; + + std::list inCombatBuffList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for (std::list::iterator itr = inCombatBuffList.begin(); itr != inCombatBuffList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) + { + if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (spells[selectedBotSpell.SpellId].buffduration < 1 || tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) + continue; + + //short duration buffs or other buffs only to be cast during combat. + 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 + } + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost); + } + + if (castedSpell) + break; + } + } + else if (botClass == BARD) { + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list inCombatBuffList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for (std::list::iterator itr = inCombatBuffList.begin(); itr != inCombatBuffList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) { + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + + // 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].targettype == ST_Pet) && (tar != this->GetPet())) + continue; + + // Validate target + + if (!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == 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 (!IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { + continue; + } + } + + if (!IsGroupSpell(selectedBotSpell.SpellId)) { + //Only check archetype if song 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)) + { + 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; + } + } + + 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; + + 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(); + + 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(); + + 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].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + if (spells[iter.SpellId].targettype != ST_Target) + continue; + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + 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].ResistDiff))) + botSpell = GetBestBotSpellForMagicBasedSlow(this); + break; + } + } + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + + if (castedSpell && GetClass() != BARD) + BotGroupSay(this, "Attempting to slow %s.", tar->GetCleanName()); + } + break; + } + case SpellType_Debuff: { + if ((tar->GetHPRatio() <= 99.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == DRUID)) && (tar->GetHPRatio() > 40.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; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Cure: { + if (GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInRaidGroup(25, false) > 0) && !(GetNumberNeedingHealedInRaidGroup(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)) { + if (this->IsRaidGrouped()) { + if (r_group) { + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + if (iter->member && !iter->member->qglobal) { + if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + iter->member->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + } + else { + if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + break; + } + 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].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + if (spells[iter.SpellId].targettype != ST_Target) + continue; + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (castedSpell) { + BotGroupSay(this, "Attempting to reduce hate on %s.", tar->GetCleanName()); + break; + } + } + } + + break; + } + case SpellType_InCombatBuffSong: { + if (GetClass() != BARD || 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].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + switch (spells[iter.SpellId].targettype) { + 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].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + switch (spells[iter.SpellId].targettype) { + 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; + } + default: + break; + } + + return castedSpell; +} + +void Raid::RaidBotGroupSay(Bot* b, uint8 language, uint8 lang_skill, const char* msg, ...) +{ + if (!b) + return; + + char buf[1000]; + va_list ap; + va_start(ap, msg); + vsnprintf(buf, 1000, msg, ap); + va_end(ap); + + uint32 groupToUse = GetGroup(b->GetName()); + + 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, b->GetName(), 64); + + strcpy(rga->message, buf); // this is safe because we are allocating enough space for the entire msg above + + worldserver.SendPacket(pack); + //safe_delete(pack); +} + +uint8 Bot::GetNumberNeedingHealedInRaidGroup(uint8 hpr, bool includePets) { + uint8 needHealed = 0; + Raid* raid = nullptr; + raid = entity_list.GetRaidByBotName(this->GetName()); + uint32 r_group = raid->GetGroup(this->GetName()); + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + //for (std::vector::iterator iter = raid_group_members.begin(); iter != raid_group_members.end(); ++iter) { + for (int i = 0; i< raid_group_members.size(); ++i) { + if (raid_group_members.at(i).member && !raid_group_members.at(i).member->qglobal) { + if (raid_group_members.at(i).member->GetHPRatio() <= hpr) + needHealed++; + + if (includePets) { + if (raid_group_members.at(i).member->GetPet() && raid_group_members.at(i).member->GetPet()->GetHPRatio() <= hpr) + needHealed++; + } + } + } + return needHealed; +} +#endif diff --git a/zone/bot_raid.h b/zone/bot_raid.h new file mode 100644 index 000000000..66d55bab8 --- /dev/null +++ b/zone/bot_raid.h @@ -0,0 +1,47 @@ +/* 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 + +#ifdef BOTS + +#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 "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 + +extern WorldServer worldserver; + +//void Bot::PetAIProcess_Raid(); + +#endif // BOTS + +#endif // BOT_RAID_H diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 22eb103c5..87835c6e6 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -3,7 +3,7 @@ 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. + the Free Software Foundation; version 2 ogroupf 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 @@ -31,6 +31,13 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { + // Bot AI + Raid* raid = entity_list.GetRaidByBotName(this->GetName()); + if (raid) { + return AICastSpell_Raid(tar, iChance, iSpellTypes); + //return true; + } + if (!tar) { return false; } diff --git a/zone/client.cpp b/zone/client.cpp index 2857f86db..f2a831a45 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -804,9 +804,10 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO // todo: save packets for later use AddPacket(app, ack_req); } - else - if(eqs) - eqs->QueuePacket(app, ack_req); + else if (eqs) // && !IsBot()) //Mitch added the BoTcheck for a fail safe on trying to send a packet to a BoT! + { + eqs->QueuePacket(app, ack_req); + } } void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) { @@ -817,7 +818,7 @@ void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CON return; } else { - if(eqs) + if(eqs) // && !IsBot()) //Mitch added eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req); else if (app && (*app)) delete *app; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index fcc2c76e2..d07920f4d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#include "bot.h" #ifdef _WINDOWS #define snprintf _snprintf @@ -72,6 +73,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifdef BOTS #include "bot.h" +#include "bot_command.h" #endif extern QueryServ* QServ; @@ -587,6 +589,31 @@ void Client::CompleteConnect() if (raid) { SetRaidGrouped(true); raid->LearnMembers(); +#ifdef BOTS + 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()); + } + } + } + } + } +#endif raid->VerifyRaid(); raid->GetRaidDetails(); /* @@ -6970,7 +6997,12 @@ void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app) } #ifdef BOTS else if (Invitee->IsBot()) { - Bot::ProcessBotGroupInvite(this, std::string(Invitee->GetName())); + Client* inviter = entity_list.GetClientByName(gis->inviter_name); + //Bot* invitee = entity_list.GetBotByBotName(gis->invitee_name); + if (inviter->IsRaidGrouped()) + Bot::ProcessRaidInvite(Invitee->CastToBot(), inviter); + else + Bot::ProcessBotGroupInvite(this, std::string(Invitee->GetName())); } #endif } @@ -11409,7 +11441,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)); @@ -11417,218 +11449,254 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) return; } - RaidGeneral_Struct *raid_command_packet = (RaidGeneral_Struct*)app->pBuffer; + RaidGeneral_Struct* 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); +#ifdef BOTS //Mitch - if (!player_to_invite) - break; + Bot* player_to_invite = nullptr; + Client* player_to_invite_owner = nullptr; - Group *player_to_invite_group = player_to_invite->GetGroup(); + if (entity_list.GetBotByBotName(raid_command_packet->player_name)) { + Bot* player_to_invite = entity_list.GetBotByBotName(raid_command_packet->player_name); + Client* player_to_invite_owner = player_to_invite->GetOwner()->CastToClient(); + 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()); + if (!player_to_invite) { break; } - if (player_to_invite_group && player_to_invite_group->IsGroupMember(this)) { - MessageString(Chat::Red, ALREADY_IN_PARTY); + //Not allowed: Invite a bot that is already within a raid. + if (player_to_invite->IsRaidGrouped()) { + MessageString(Chat::White, ALREADY_IN_RAID, GetName()); //must invite members not in raid... + return; + } + + // Not allowed: Invite a bot that is not owned by the invitor + if (player_to_invite->IsBot() && + player_to_invite->CastToBot()->GetOwner()->CastToClient()->CharacterID() != + player_to_invite_owner->CharacterID()) { + Message(Chat::Red, "%s is not your Bot. You can only invite your Bots, or players grouped with bots.", player_to_invite->GetName()); + } + + // Not allowed: Invite a bot that is in a group but the bot is not the group leader + if (player_to_invite_group && !player_to_invite_group->IsLeader(player_to_invite->CastToMob())) { + Message(Chat::Red, "You can only invite group leaders or ungrouped bots. Try %s instead.", player_to_invite_group->GetLeader()->GetName()); break; } - 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."); + Bot::ProcessRaidInvite(player_to_invite, player_to_invite_owner); + break; + } + else + { +#endif + 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()); + break; + } + + if (player_to_invite_group && player_to_invite_group->IsGroupMember(this)) { + 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; + } + + /* Send out invite to the client */ + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); + 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); + + raid_command->parameter = 0; + raid_command->action = 20; + + player_to_invite->QueuePacket(outapp); + + safe_delete(outapp); + break; } + } - /* Send out invite to the client */ - auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); - RaidGeneral_Struct *raid_command = (RaidGeneral_Struct*)outapp->pBuffer; + case RaidCommandAcceptInvite: { + Client* player_accepting_invite = entity_list.GetClientByName(raid_command_packet->player_name); - strn0cpy(raid_command->leader_name, raid_command_packet->leader_name, 64); - strn0cpy(raid_command->player_name, raid_command_packet->player_name, 64); +#ifdef BOTS + // 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); + Group* g_invitee = invitee->GetGroup(); + Group* g_invitor = invitor->GetGroup(); - raid_command->parameter = 0; - raid_command->action = 20; + if (invitee && invitee->IsRaidGrouped()) { + invitor->MessageString(Chat::White, ALREADY_IN_RAID, GetName()); //group failed, must invite members not in raid... + return; + } - player_to_invite->QueuePacket(outapp); - - safe_delete(outapp); + bool invitor_has_bot = false; + bool invitee_has_bot = false; + if (g_invitor && g_invitor->IsLeader(invitor)) + { + for (int x = 0; x < 6; x++) + { + if (g_invitor->members[x] && g_invitor->members[x]->IsBot()) + { + b = entity_list.GetBotByBotName(g_invitor->members[x]->GetName()); + invitee_has_bot = true; + } + } + } + if (g_invitee && g_invitee->IsLeader(invitee)) + { + for (int x = 0; x < 6; x++) + { + if (g_invitee->members[x] && g_invitee->members[x]->IsBot()) + { + b = entity_list.GetBotByBotName(g_invitee->members[x]->GetName()); + 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; } - 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; + else if (invitee->IsBot()) + { + Bot::ProcessRaidInvite(b, player_accepting_invite); //client inviting a bot + break; + } + +#endif + 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; + } + 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; + } } - 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; - } + 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; } - 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; - } - } - 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; - if (group->members[x]->IsClient()) - c = group->members[x]->CastToClient(); - else - continue; + } + 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; + if (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); + else + continue; - if (!addClient) - { - addClient = c; - raid->SetGroupLeader(addClient->GetName()); - } + if (!addClient) + { + addClient = c; + raid->SetGroupLeader(addClient->GetName()); + } - 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->SendBulkRaid(c); - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } + 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->SendBulkRaid(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(free_group_id); + } + else { + raid->SendRaidCreate(this); + raid->SendMakeLeaderPacketTo(raid->leadername, this); + raid->AddMember(this); + raid->SendBulkRaid(this); + if (raid->IsLocked()) { + raid->SendRaidLockTo(this); } } - else + } + else + { + Group* player_invited_group = player_accepting_invite->GetGroup(); + Group* group = GetGroup(); + if (group) //if our target has a group { - 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); - entity_list.AddRaid(raid); - raid->SetRaidDetails(); + raid = new Raid(player_accepting_invite); + entity_list.AddRaid(raid); + raid->SetRaidDetails(); - uint32 raid_free_group_id = raid->GetFreeGroup(); + 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); - } - } - } - } - 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); - } - - Client *client_to_add = nullptr; - /* Add client to an existing group */ + /* 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 (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 (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 (group->IsLeader(group->members[x])) { - Client *c = nullptr; + if (player_invited_group->IsLeader(player_invited_group->members[x])) { + Client* c = nullptr; - if (group->members[x]->IsClient()) - c = group->members[x]->CastToClient(); + 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, false, true); + raid->AddMember(c, raid_free_group_id, true, true, true); raid->SendBulkRaid(c); if (raid->IsLocked()) { raid->SendRaidLockTo(c); } } - else - { - Client *c = nullptr; + else { + Client* c = nullptr; - if (group->members[x]->IsClient()) - c = group->members[x]->CastToClient(); + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); else continue; @@ -11643,396 +11711,536 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) } } } - group->JoinRaidXTarget(raid); - group->DisbandGroup(true); - + player_invited_group->JoinRaidXTarget(raid, true); + player_invited_group->DisbandGroup(true); raid->GroupUpdate(raid_free_group_id); + raid_free_group_id = raid->GetFreeGroup(); } - /* Target does not have a group */ else { - if (player_invited_group) { + raid->SendRaidCreate(player_accepting_invite); + raid->AddMember(player_accepting_invite, 0xFFFFFFFF, true, false, true); + } - raid = new Raid(player_accepting_invite); + 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; - 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 (group->members[x]->IsClient()) + c = group->members[x]->CastToClient(); + else + continue; + + raid->SendRaidCreate(c); + raid->SendMakeLeaderPacketTo(raid->leadername, c); + raid->AddMember(c, raid_free_group_id, false, true); + raid->SendBulkRaid(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); + raid->AddMember(c, raid_free_group_id); + raid->SendBulkRaid(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->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); - } - } + if (player_invited_group->members[x]->IsClient()) + c = player_invited_group->members[x]->CastToClient(); else - { - Client *c = nullptr; - if (player_invited_group->members[x]->IsClient()) - c = player_invited_group->members[x]->CastToClient(); - else - continue; + continue; - raid->SendRaidCreate(c); - raid->SendMakeLeaderPacketTo(raid->leadername, c); - raid->AddMember(c, 0); - raid->SendBulkRaid(c); - if (raid->IsLocked()) { - raid->SendRaidLockTo(c); - } + 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); - } } - 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); - } + 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); + } + } + 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); } } } } - break; } - case RaidCommandDisband: { - Raid *raid = entity_list.GetRaidByClient(this); - if (raid) { - uint32 group = raid->GetGroup(raid_command_packet->leader_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; - } - } - } + 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); + Bot* b_to_disband = entity_list.GetBotByBotName(raid_command_packet->leader_name); + + if (raid) { + uint32 group = raid->GetGroup(raid_command_packet->leader_name); +#ifdef BOTS + //Mitch added to remove all bots if the Bot_Owner is removed from the Raid + //Does not camp the Bots, just removes from the raid + std::vector raid_members_bots; + if (c_to_disband) + { + // Determine if the client has any BOTS in the raid + uint32 owner_id = c_to_disband->CharacterID(); + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + { + if (raid->members[i].member && raid->members[i].member->IsBot() && raid->members[i].member->CastToBot()->GetOwner()->CastToClient()->CharacterID() == owner_id) + { + raid_members_bots.emplace_back(raid->members[i].member->CastToBot()); } - 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(); + } + // 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) { + if (bot_iter && raid->IsRaidMember(bot_iter->GetName()) && raid->IsGroupLeader(bot_iter->GetName())) + { + // Remove the entire BOT group in this case + uint32 gid = raid->GetGroup(bot_iter->GetName()); + + std::vector r_group_members = raid->GetRaidGroupMembers(gid); + Group* 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->GetCleanName()); + for (auto member_iter : r_group_members) { + if (!member_iter.member->IsClient() && strcmp(member_iter.membername, bot_iter->GetName()) == 0) + bot_iter->SetFollowID(owner_id); + + else + Bot::AddBotToGroup(member_iter.member->CastToBot(), group_inst); + raid->RemoveMember(bot_iter->GetName()); + } + } + else if (bot_iter && raid->IsRaidMember(bot_iter->GetName())) + { + raid->RemoveMember(bot_iter->GetName()); + } + } + } + else if (b_to_disband) + { + uint32 gid = raid->GetGroup(b_to_disband->GetName()); + if (gid < 12 && raid->IsGroupLeader(b_to_disband->GetName())) + { + // If any of the bots are a group leader then re-create the botgroup on disband, dropping any clients + std::vector r_group_members = raid->GetRaidGroupMembers(gid); + uint32 owner_id = b_to_disband->CastToBot()->GetOwner()->CastToClient()->CharacterID(); + if (raid->IsGroupLeader(b_to_disband->GetName())) + { + // Remove the entire BOT group in this case + //uint32 gid = raid->GetGroup(b_to_disband->GetName()); + //std::vector r_group_members = raid->GetRaidGroupMembers(gid); + Group* group_inst = new Group(b_to_disband); + entity_list.AddGroup(group_inst); + database.SetGroupID(b_to_disband->GetCleanName(), group_inst->GetID(), b_to_disband->GetBotID()); + database.SetGroupLeaderName(group_inst->GetID(), b_to_disband->GetCleanName()); + for (auto member_iter : r_group_members) { + if (!member_iter.member->IsClient() && strcmp(member_iter.membername, b_to_disband->GetName()) == 0) + b_to_disband->SetFollowID(owner_id); + else + Bot::AddBotToGroup(member_iter.member->CastToBot(), group_inst); + raid->RemoveMember(member_iter.member->CastToBot()->GetName()); + } + } + break; + } + else if (gid <12 && raid->GetGroupLeader(gid)->IsBot()) + { + c_doing_disband->Message(Chat::Yellow, "%s is in a Bot Group. Please disband %s instead to remove the entire Bot group.", + raid_command_packet->leader_name, raid->GetGroupLeader(gid)->CastToBot()->GetName()); + break; + } + } +#endif + 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 - //} - } - 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. + 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(); 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); + } + 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 + if (!raid->RaidCount()) + raid->DisbandRaid(); + } + 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 (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, 0xFFFFFFFF); + 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/effects.cpp b/zone/effects.cpp index aa41f35a0..1499e4fd6 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -31,6 +31,8 @@ #include "zone_store.h" #include "position.h" +extern bool Critical; //Mitch + float Mob::GetActSpellRange(uint16 spell_id, float range, bool IsBard) { float extrange = 100; @@ -316,6 +318,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { value += value * CastToNPC()->GetSpellFocusHeal() / 100; } + Critical = false; int32 base_value = value; int16 critical_chance = 0; int8 critical_modifier = 1; diff --git a/zone/entity.cpp b/zone/entity.cpp index 0aa2aba78..e1270a49c 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2088,17 +2088,17 @@ 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) { + for (auto& member : (*iterator)->members) { if (member.member) { if (member.member == client) { client->p_raid_instance = *iterator; @@ -2113,6 +2113,48 @@ Raid *EntityList::GetRaidByClient(Client* client) return nullptr; } +Raid* EntityList::GetRaidByBotName(const char* name) +{ + + std::list::iterator iterator; + iterator = raid_list.begin(); + + while (iterator != raid_list.end()) { + for (auto& member : (*iterator)->members) { + if (member.membername) { + if (strcmp(member.membername, name) == 0) { + //client->p_raid_instance = *iterator; + return *iterator; + } + } + } + + ++iterator; + } + + return nullptr; +} + +Raid* EntityList::GetRaidByBot(Bot* bot) +{ + + std::list::iterator iterator; + iterator = raid_list.begin(); + + while (iterator != raid_list.end()) { + for (auto& member : (*iterator)->members) { + if (member.member && member.member->CastToBot() == bot) { + bot->p_raid_instance = *iterator; + return *iterator; + } + } + ++iterator; + } + return nullptr; +} + + + Raid *EntityList::GetRaidByMob(Mob *mob) { std::list::iterator iterator; diff --git a/zone/entity.h b/zone/entity.h index bc44cb2c7..540ec1c19 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -198,6 +198,10 @@ public: Raid *GetRaidByClient(Client* client); Raid *GetRaidByID(uint32 id); Raid *GetRaidByLeaderName(const char *leader); +#ifdef BOTS + Raid* GetRaidByBotName(const char* name); + Raid* GetRaidByBot(Bot* bot); +#endif Corpse *GetCorpseByOwner(Client* client); Corpse *GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range); diff --git a/zone/exp.cpp b/zone/exp.cpp index b7261ccf7..045a9bba6 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -1097,7 +1097,7 @@ void Raid::SplitExp(uint32 exp, Mob* other) { return; for (unsigned int x = 0; x < MAX_RAID_MEMBERS; x++) { - if (members[x].member != nullptr) // If Group Member is Client + if (members[x].member != nullptr && members[x].member->CastToBot()->GetBotID() == 0) // If Group Member is Client { Client *cmember = members[x].member; // add exp + exp cap diff --git a/zone/groups.cpp b/zone/groups.cpp index 40110b10e..f2867f9d1 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -248,10 +248,24 @@ 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)) +#ifdef BOTSS + if (newmember->IsBot() && !newmember->HasGroup() && !strcasecmp(membername[i], NewMemberName)) // Mitch + { + //Bot::RemoveBotFromGroup(newmember->CastToBot(), members[0]->GetGroup()); + //Group::DelMember(newmember); + memset(membername[i], 0, 64); + members[i] = nullptr; + } + else if (!strcasecmp(membername[i], NewMemberName)) { return false; } +#else + if (!strcasecmp(membername[i], NewMemberName)) + { + return false; + } +#endif } // Put them in the group @@ -1140,7 +1154,13 @@ 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()); + //std::string query = StringFormat("SELECT name FROM group_id WHERE groupid = %lu", (unsigned long)GetID()); + std::string query = StringFormat("SELECT name FROM group_id " + "WHERE group_id.groupid = %lu AND group_id.name NOT " + "IN(SELECT group_leaders.leadername FROM group_leaders WHERE gid = %lu)" + , (unsigned long)GetID() + , (unsigned long)GetID()); + auto results = database.QueryDatabase(query); if (!results.Success()) return false; @@ -1155,7 +1175,7 @@ bool Group::LearnMembers() { return false; } - int memberIndex = 0; + int memberIndex = 1; //starts at 1 becasuse leader [0] is done specifically for(auto row = results.begin(); row != results.end(); ++row) { if(!row[0]) continue; @@ -1165,7 +1185,24 @@ bool Group::LearnMembers() { memberIndex++; } + // for leader only [0] /Mitch + query = StringFormat("SELECT leadername FROM group_leaders WHERE group_leaders.gid = %lu", (unsigned long)GetID()); + auto results2 = database.QueryDatabase(query); + if (!results2.Success()) + return false; + if (results2.RowCount() == 0) { + LogError( + "Error getting group leader for group [{}]: [{}]", + (unsigned long)GetID(), + results2.ErrorMessage().c_str() + ); + + return false; + } + auto row2 = results2.begin(); + members[0] = nullptr; + strn0cpy(membername[0], row2[0], 64); return true; } @@ -1176,6 +1213,22 @@ void Group::VerifyGroup() { Only called every once in a while (on member re-join for now). */ + // To do + // Reset the membername array to match the group_id database records? + // When doing this manually, it seem to have resolved the issue. + // Only want to do this when the database Name does not match the array + // Could this be done from the LearnGroup method? + // reset the members and membername array for this group + // Mitch + for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { + members[i] = nullptr; + memset(membername[i],'\0',64); + //membername[i][0] == '\0'); + } + // repopulate the membername array from the database to ensure the local zone instance has accurate information + + Group::LearnMembers(); + uint32 i; for (i = 0; i < MAX_GROUP_MEMBERS; i++) { if (membername[i][0] == '\0') { @@ -1195,6 +1248,10 @@ void Group::VerifyGroup() { members[i] = nullptr; continue; } + //if (them == nullptr && members[i] == nullptr) { //fixes a group bug with bots Mitch added Jan 1 2022 + // membername[i][0] = '\0'; + // continue; + //} if(them != nullptr && members[i] != them) { //our pointer is out of date... not so good. #if EQDEBUG >= 5 diff --git a/zone/main.cpp b/zone/main.cpp index f92ea9ea8..fa6d47259 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -88,6 +88,7 @@ volatile bool RunLoops = true; #endif extern volatile bool is_zone_loaded; +extern bool Critical = false; EntityList entity_list; WorldServer worldserver; diff --git a/zone/mob.cpp b/zone/mob.cpp index 6f180d768..306421057 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3855,7 +3855,7 @@ void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::O const char *Mob::GetCleanName() { - if (!strlen(clean_name)) { + if (clean_name != NULL && !strlen(clean_name)) { //extra check added for crash condition. Mitch CleanMobName(GetName(), clean_name); } diff --git a/zone/raids.cpp b/zone/raids.cpp index 712d0a1d5..56d5b31c8 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -24,6 +24,7 @@ #include "groups.h" #include "mob.h" #include "raids.h" +#include "bot.h" //Mitch #include "worldserver.h" @@ -97,7 +98,7 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo 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", + "isgroupleader = %d, israidleader = %d, islooter = %d, isbot = 0", (unsigned long)GetID(), (unsigned long)c->CharacterID(), (unsigned long)group, c->GetClass(), c->GetLevel(), c->GetName(), groupleader, rleader, looter); @@ -109,6 +110,14 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo LearnMembers(); VerifyRaid(); + +#ifdef BOTS + if (rleader) { + database.SetRaidGroupLeaderInfo(group, GetID()); + UpdateRaidAAs(); + } + else +#endif if (rleader) { database.SetRaidGroupLeaderInfo(RAID_GROUPLESS, GetID()); UpdateRaidAAs(); @@ -162,6 +171,86 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo worldserver.SendPacket(pack); safe_delete(pack); } +//Mitch +void Raid::AddBot(Bot* b, uint32 group, bool rleader, bool groupleader, bool looter) { + if (!b) + 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, isbot = 1", + (unsigned long)GetID(), (unsigned long)b->GetBotID(), + (unsigned long)group, b->GetClass(), b->GetLevel(), + b->GetName(), groupleader, rleader, looter); + auto results = database.QueryDatabase(query); + + if (!results.Success()) { + LogError("Error inserting into raid members: [{}]", results.ErrorMessage().c_str()); + } + + LearnMembers(); + VerifyRaid(); +// Bots are being invited and cannot be the raid leader +// if (rleader) { +// database.SetRaidGroupLeaderInfo(RAID_GROUPLESS, GetID()); +// UpdateRaidAAs(); +// } + +// Bots can be group leaders, though they do not have GroupAA +// if (group != RAID_GROUPLESS && groupleader) { +// database.SetRaidGroupLeaderInfo(group, GetID()); +// UpdateGroupAAs(group); //Mitch Jan 22 +// } + + if (group < 12) //Jan22 + GroupUpdate(group); //Jan22 + else // get raid AAs, GroupUpdate will handles it otherwise Jan 22 + SendGroupLeadershipAA(b->GetOwner()->CastToClient(), RAID_GROUPLESS); //Is this needed for bots? Jan 22 + SendRaidAddAll(b->GetName()); + + b->SetRaidGrouped(true); + b->p_raid_instance = this; + //SendRaidMOTD(b->GetOwner()->CastToClient()); + + // Mitch What to do here? + // xtarget shit .......... + //if (group == RAID_GROUPLESS) { + // if (rleader) { + // GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr()); + // c->GetXTargetAutoMgr()->clear(); + // c->SetXTargetAutoMgr(GetXTargetAutoMgr()); + // } + // else { + // if (!c->GetXTargetAutoMgr()->empty()) { + // GetXTargetAutoMgr()->merge(*c->GetXTargetAutoMgr()); + // c->GetXTargetAutoMgr()->clear(); + // c->RemoveAutoXTargets(); + // } + + // c->SetXTargetAutoMgr(GetXTargetAutoMgr()); + + // if (!c->GetXTargetAutoMgr()->empty()) + // c->SetDirtyAutoHaters(); + // } + //} + +// Raid* raid_update = nullptr; +// raid_update = b->GetOwner()->GetRaid(); +// if (raid_update) { +// raid_update->SendHPManaEndPacketsTo(b->GetOwner()->CastToClient()); +// raid_update->SendHPManaEndPacketsFrom(b->GetOwner()->CastToClient()); +// } + + auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct)); + ServerRaidGeneralAction_Struct* rga = (ServerRaidGeneralAction_Struct*)pack->pBuffer; + rga->rid = GetID(); + strn0cpy(rga->playername, b->GetName(), 64); + rga->zoneid = zone->GetZoneID(); + rga->instance_id = zone->GetInstanceID(); + worldserver.SendPacket(pack); + safe_delete(pack); +} + void Raid::RemoveMember(const char *characterName) { @@ -169,6 +258,17 @@ void Raid::RemoveMember(const char *characterName) auto results = database.QueryDatabase(query); Client *client = entity_list.GetClientByName(characterName); +#ifdef BOTS + Bot* bot = entity_list.GetBotByBotName(characterName); + + if (bot) { + bot->SetFollowID(bot->GetOwner()->CastToClient()->GetID()); + bot->SetGrouped(false); + bot->SetTarget(nullptr); + bot->SetRaidGrouped(false); + } +#endif + disbandCheck = true; SendRaidRemoveAll(characterName); SendRaidDisband(client); @@ -963,7 +1063,7 @@ void Raid::SendRaidAdd(const char *who, Client *to) for(int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(strcmp(members[x].membername, who) == 0) + if(strcmp(members[x].membername, who) == 0)// || !members[x].SentToBotOwner) //Mitch { auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidAddMember_Struct)); RaidAddMember_Struct *ram = (RaidAddMember_Struct*)outapp->pBuffer; @@ -983,12 +1083,12 @@ void Raid::SendRaidAdd(const char *who, Client *to) void Raid::SendRaidAddAll(const char *who) { - for(int x = 0; x < MAX_RAID_MEMBERS; x++) + for (int x = 0; x < MAX_RAID_MEMBERS; x++) { - if(strcmp(members[x].membername, who) == 0) + if (strcmp(members[x].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); @@ -999,6 +1099,7 @@ void Raid::SendRaidAddAll(const char *who) this->QueuePacket(outapp); safe_delete(outapp); return; + } } } @@ -1111,11 +1212,14 @@ void Raid::SendBulkRaid(Client *to) if(!to) return; + if (members[GetPlayerIndex(to)].IsBot) + return; + 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(members[x].member && strlen(members[x].membername) > 0 && (strcmp(members[x].membername, to->GetName()) != 0)) //don't send ourself { - SendRaidAdd(members[x].membername, to); + SendRaidAdd(members[x].membername, to); } } } @@ -1124,7 +1228,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); } @@ -1133,6 +1237,11 @@ void Raid::QueuePacket(const EQApplicationPacket *app, bool ack_req) void Raid::SendMakeLeaderPacket(const char *who) //30 { + + //if (entity_list.GetBotByBotName(who) && IsRaidMemberBot(entity_list.GetBotByBotName(who)->CastToClient())) + if (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; @@ -1148,6 +1257,9 @@ void Raid::SendMakeLeaderPacketTo(const char *who, Client *to) if(!to) return; + if (members[GetPlayerIndex(who)].IsBot) + return; + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct)); RaidLeadershipUpdate_Struct *rg = (RaidLeadershipUpdate_Struct*)outapp->pBuffer; rg->action = raidMakeLeader; @@ -1178,6 +1290,9 @@ void Raid::SendGroupUpdate(Client *to) if(!to) return; + if (members[GetPlayerIndex(to)].IsBot) + return; + auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupUpdate2_Struct)); GroupUpdate2_Struct* gu = (GroupUpdate2_Struct*)outapp->pBuffer; gu->action = groupActUpdate; @@ -1224,7 +1339,7 @@ void Raid::GroupUpdate(uint32 gid, bool initial) { 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); } @@ -1366,6 +1481,9 @@ void Raid::SendRaidMOTDToWorld() void Raid::SendGroupLeadershipAA(Client *c, uint32 gid) { + if (members[GetPlayerIndex(c)].IsBot) + return; + auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidLeadershipUpdate_Struct)); RaidLeadershipUpdate_Struct *rlaa = (RaidLeadershipUpdate_Struct *)outapp->pBuffer; rlaa->action = raidSetLeaderAbilities; @@ -1381,17 +1499,46 @@ 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) + if (members[i].member && members[i].GroupNumber == gid && !members[i].IsBot) SendGroupLeadershipAA(members[i].member, gid); } void Raid::SendAllRaidLeadershipAA() { for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) - if (members[i].member) + if (members[i].member && !members[i].IsBot) SendGroupLeadershipAA(members[i].member, members[i].GroupNumber); } +bool Raid::IsRaidMemberBot(Client* client) +{ + std::string query = StringFormat("SELECT mob_type FROM vw_bot_character_mobs WHERE name = '%s' LIMIT 1", + client->GetCleanName()); + auto results = database.QueryDatabase(query); + + if (!results.Success()) + return true; //return true to avoid sending a packet to a non-existant client as a failsafe + + if (results.RowCount() == 0) { + LogError( + "Error getting B/C info for character name [{}] for IsRaidMemberBot. Error [{}]", + client->GetCleanName(), + results.ErrorMessage().c_str() + ); + return true;//return true to avoid sending a packet to a non-existant client as a failsafe + } + + auto row = results.begin(); + const char* c = "C"; + const char* b = "B"; + if (strcmp(row[0], c) == 0) { + return false; // client is a client + } + else if (strcmp(row[0], b) == 0) { + return true; // client is actually a bot + } +} + void Raid::LockRaid(bool lockFlag) { std::string query = StringFormat("UPDATE raid_details SET locked = %d WHERE raidid = %lu", @@ -1458,7 +1605,7 @@ bool Raid::LearnMembers() memset(members, 0, (sizeof(RaidMember)*MAX_RAID_MEMBERS)); std::string query = StringFormat("SELECT name, groupid, _class, level, " - "isgroupleader, israidleader, islooter " + "isgroupleader, israidleader, islooter, isbot " "FROM raid_members WHERE raidid = %lu", (unsigned long)GetID()); auto results = database.QueryDatabase(query); @@ -1489,6 +1636,7 @@ bool Raid::LearnMembers() members[index].IsGroupLeader = atoi(row[4]); members[index].IsRaidLeader = atoi(row[5]); members[index].IsLooter = atoi(row[6]); + members[index].IsBot = atoi(row[7]); //Mitch ++index; } @@ -1504,14 +1652,25 @@ void Raid::VerifyRaid() } else{ Client *c = entity_list.GetClientByName(members[x].membername); +#ifdef BOTS + Bot* b = entity_list.GetBotByBotName(members[x].membername); //Mitch +#endif if(c){ members[x].member = c; + members[x].IsBot = false; } - else{ +#ifdef BOTS + else if(b){ + members[x].member = b->CastToClient(); //Raid requires client* we are forcing it here to be a BOT + members[x].IsBot = true; //Used to identify those members who are Bots + } +#endif + else { members[x].member = nullptr; + members[x].IsBot = false; } } - if(members[x].IsRaidLeader){ + if(members[x].IsRaidLeader){ if(strlen(members[x].membername) > 0){ SetLeader(members[x].member); strn0cpy(leadername, members[x].membername, 64); @@ -1561,7 +1720,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); @@ -1603,7 +1762,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) { @@ -1636,7 +1795,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); @@ -1663,7 +1822,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); @@ -1770,9 +1929,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*/) { @@ -1791,6 +1955,9 @@ void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_re if (!members[i].member->IsClient()) continue; + if (members[i].IsBot) + continue; + if (ignore_sender && members[i].member == sender) continue; @@ -1853,3 +2020,15 @@ 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; +} diff --git a/zone/raids.h b/zone/raids.h index e1d1cadb3..cfaea07e7 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -88,6 +88,16 @@ struct RaidMember{ bool IsGroupLeader; bool IsRaidLeader; bool IsLooter; +#ifdef BOTS + bool IsBot = false; + bool IsGroupHealer; + bool IsRaidSlower; + bool IsRaidMainAssistOne; + bool IsRaidMainAssistTwo; + bool IsRaidMainTank; + bool IsRaidOffTankOne; + bool IsRaidOffTankTwo; +#endif }; struct GroupMentor { @@ -112,6 +122,12 @@ public: bool IsRaid() { return true; } void AddMember(Client *c, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); +#ifdef BOTS + void AddBot(Bot* b, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); //Mitch + void RaidBotGroupSay(Bot* b, uint8 language, uint8 lang_skill, const char* msg, ...); //Mitch + static Mob* GetRaidMainAssistOneByName(const char* name); + bool IsRaidMemberBot(Client* client); +#endif void RemoveMember(const char *c); void DisbandRaid(); void MoveMember(const char *name, uint32 newGroup); @@ -121,6 +137,7 @@ public: bool IsGroupLeader(const char *who); bool IsRaidMember(const char *name); void UpdateLevel(const char *name, int newLevel); + uint32 GetFreeGroup(); uint8 GroupCount(uint32 gid); @@ -241,6 +258,7 @@ 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); RaidMember members[MAX_RAID_MEMBERS]; char leadername[64]; diff --git a/zone/spells.cpp b/zone/spells.cpp index 0c2a762a6..276e49e8e 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3928,8 +3928,24 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); } else { +#ifndef BOTS MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name); spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); +#endif +#ifdef BOTS + if (this->IsBot() && IsHarmonySpell(spell_id)) { + if (IsGrouped() && this->GetGroup()->GetLeader()->IsClient()) { + Bot::BotGroupSay(this, "Your target RESISTED %s", spells[spell_id].name); + } + else { + this->CastToBot()->GetBotOwner()->MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name); + } + } + else { + MessageString(Chat::SpellFailure, TARGET_RESISTED, spells[spell_id].name); + spelltar->MessageString(Chat::SpellFailure, YOU_RESIST, spells[spell_id].name); + } +#endif } if (spelltar->IsAIControlled()) { @@ -3957,7 +3973,16 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes return false; } } - +#ifdef BOTS //Added to display when a HarmonySpell was successful from a bot + if (this->IsBot() && IsHarmonySpell(spell_id)) { + if (IsGrouped() && this->GetGroup()->GetLeader()->IsClient()) { + Bot::BotGroupSay(this, "Your spell was mostly successful."); + } + else { + this->CastToBot()->GetBotOwner()->MessageString(Chat::SpellFailure, SLOW_MOSTLY_SUCCESSFUL); + } + } +#endif if (spelltar->IsClient()){ spelltar->CastToClient()->BreakSneakWhenCastOn(this, false); spelltar->CastToClient()->BreakFeignDeathWhenCastOn(false);