From dde16674c3725b2be1ee393a2d6cfb0481dee962 Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Mon, 8 Dec 2025 04:34:16 -0700 Subject: [PATCH] Fix: Stop pets from fighting in PVP prohibited zones (#1829) Stripped down version of #1818. No new features. Refactors IsPossibleTarget in AttackersValue.cpp to a better style and makes sure pets don't attack in prohibited zones. Testing: Confirmed that aggro pets no longer attack in PVP prohibited areas, but still do outside them. Zim'Torga in Zul'Drak is a good example to test this (ID 4323). Lookout for death knights with a Risen Ally (uncontrolled and naturally aggro) now they respect PVP prohibition like their master. Note: If you manually teleport a bot that is in mid combat to a PVP prohibited area, its aggro pet might still attack, because its master is still in combat strategy. Otherwise the pet will not attack if its master has switched to non-combat. --- ...11_25_00_ai_playerbot_pet_action_texts.sql | 255 ++++++++++++++++++ src/strategy/actions/AttackAction.cpp | 7 +- src/strategy/actions/PetsAction.cpp | 97 +++++-- src/strategy/values/AttackersValue.cpp | 139 ++++++---- 4 files changed, 428 insertions(+), 70 deletions(-) create mode 100644 data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql diff --git a/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql b/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql new file mode 100644 index 00000000..b7c6c968 --- /dev/null +++ b/data/sql/playerbots/updates/2025_11_25_00_ai_playerbot_pet_action_texts.sql @@ -0,0 +1,255 @@ +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'pet_usage_error', + 'pet_no_pet_error', + 'pet_stance_report', + 'pet_no_target_error', + 'pet_target_dead_error', + 'pet_invalid_target_error', + 'pet_pvp_prohibited_error', + 'pet_attack_success', + 'pet_attack_failed', + 'pet_follow_success', + 'pet_stay_success', + 'pet_unknown_command_error', + 'pet_stance_set_success', + 'pet_type_pet', + 'pet_type_guardian', + 'pet_stance_aggressive', + 'pet_stance_defensive', + 'pet_stance_passive', + 'pet_stance_unknown' +); + +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'pet_usage_error', + 'pet_no_pet_error', + 'pet_stance_report', + 'pet_no_target_error', + 'pet_target_dead_error', + 'pet_invalid_target_error', + 'pet_pvp_prohibited_error', + 'pet_attack_success', + 'pet_attack_failed', + 'pet_follow_success', + 'pet_stay_success', + 'pet_unknown_command_error', + 'pet_stance_set_success', + 'pet_type_pet', + 'pet_type_guardian', + 'pet_stance_aggressive', + 'pet_stance_defensive', + 'pet_stance_passive', + 'pet_stance_unknown' +); + +INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES +(1717, 'pet_usage_error', "Usage: pet ", 0, 0, +"사용법: pet ", +"Utilisation: pet ", +"Verwendung: pet ", +"用法: pet ", +"用法: pet ", +"Uso: pet ", +"Uso: pet ", +"Использование: pet "), + +(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0, +"펫이나 수호자 펫이 없습니다.", +"Vous n'avez pas de familier ou gardien.", +"Du hast kein Tier oder Wächter.", +"你没有宠物或守护者宠物。", +"你沒有寵物或守護者寵物。", +"No tienes mascota o mascota guardián.", +"No tienes mascota o mascota guardián.", +"У вас нет питомца или защитника."), + +(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0, +"%type \"%name\"의 현재 태세: %stance.", +"Position actuelle du %type \"%name\": %stance.", +"Aktuelle Haltung des %type \"%name\": %stance.", +"%type \"%name\" 的当前姿态: %stance。", +"%type \"%name\" 的當前姿態: %stance。", +"Postura actual del %type \"%name\": %stance.", +"Postura actual del %type \"%name\": %stance.", +"Текущая позиция %type \"%name\": %stance."), + +(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0, +"주인이 유효한 대상을 선택하지 않았습니다.", +"Aucune cible valide sélectionnée par le maître.", +"Kein gültiges Ziel vom Meister ausgewählt.", +"主人未选择有效目标。", +"主人未選擇有效目標。", +"No hay objetivo válido seleccionado por el maestro.", +"No hay objetivo válido seleccionado por el maestro.", +"Хозяин не выбрал действительную цель."), + +(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0, +"대상이 살아있지 않습니다.", +"La cible n'est pas vivante.", +"Das Ziel ist nicht am Leben.", +"目标未存活。", +"目標未存活。", +"El objetivo no está vivo.", +"El objetivo no está vivo.", +"Цель не жива."), + +(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0, +"대상이 봇에게 유효한 공격 대상이 아닙니다.", +"La cible n'est pas une cible d'attaque valide pour le bot.", +"Das Ziel ist kein gültiges Angriffsziel für den Bot.", +"目标不是机器人的有效攻击目标。", +"目標不是機器人的有效攻擊目標。", +"El objetivo no es un objetivo de ataque válido para el bot.", +"El objetivo no es un objetivo de ataque válido para el bot.", +"Цель не является допустимой целью атаки для бота."), + +(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0, +"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.", +"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.", +"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.", +"我不能命令我的宠物在禁止PvP的区域攻击玩家。", +"我不能命令我的寵物在禁止PvP的區域攻擊玩家。", +"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.", +"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.", +"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."), + +(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0, +"펫이 당신의 대상을 공격하도록 명령했습니다.", +"Le familier a reçu l'ordre d'attaquer votre cible.", +"Tier wurde befohlen, dein Ziel anzugreifen.", +"宠物已命令攻击你的目标。", +"寵物已命令攻擊你的目標。", +"Mascota ordenada a atacar tu objetivo.", +"Mascota ordenada a atacar tu objetivo.", +"Питомцу приказано атаковать вашу цель."), + +(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0, +"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)", +"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)", +"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)", +"宠物未攻击。(已在攻击或无法攻击目标)", +"寵物未攻擊。(已在攻擊或無法攻擊目標)", +"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)", +"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)", +"Питомец не атаковал. (Уже атакует или не может атаковать цель)"), + +(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0, +"펫이 따라오도록 명령했습니다.", +"Le familier a reçu l'ordre de suivre.", +"Tier wurde befohlen zu folgen.", +"宠物已命令跟随。", +"寵物已命令跟隨。", +"Mascota ordenada a seguir.", +"Mascota ordenada a seguir.", +"Питомцу приказано следовать."), + +(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0, +"펫이 머물도록 명령했습니다.", +"Le familier a reçu l'ordre de rester.", +"Tier wurde befohlen zu bleiben.", +"宠物已命令停留。", +"寵物已命令停留。", +"Mascota ordenada a quedarse.", +"Mascota ordenada a quedarse.", +"Питомцу приказано остаться."), + +(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet ", 0, 0, +"알 수 없는 펫 명령: %param. 사용법: pet ", +"Commande de familier inconnue: %param. Utilisation: pet ", +"Unbekannter Tierbefehl: %param. Verwendung: pet ", +"未知宠物命令: %param。用法: pet ", +"未知寵物命令: %param。用法: pet ", +"Comando de mascota desconocido: %param. Uso: pet ", +"Comando de mascota desconocido: %param. Uso: pet ", +"Неизвестная команда питомца: %param. Использование: pet "), + +(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0, +"펫 태세가 %stance(으)로 설정되었습니다.", +"Position du familier définie sur %stance.", +"Tierhaltung auf %stance gesetzt.", +"宠物姿态设置为 %stance。", +"寵物姿態設置為 %stance。", +"Postura de mascota establecida en %stance.", +"Postura de mascota establecida en %stance.", +"Позиция питомца установлена на %stance."), + +(1730, 'pet_type_pet', "pet", 0, 0, +"펫", +"familier", +"Tier", +"宠物", +"寵物", +"mascota", +"mascota", +"питомец"), + +(1731, 'pet_type_guardian', "guardian", 0, 0, +"수호자", +"gardien", +"Wächter", +"守护者", +"守護者", +"guardián", +"guardián", +"защитник"), + +(1732, 'pet_stance_aggressive', "aggressive", 0, 0, +"공격적", +"agressif", +"aggressiv", +"进攻", +"進攻", +"agresivo", +"agresivo", +"агрессивная"), + +(1733, 'pet_stance_defensive', "defensive", 0, 0, +"방어적", +"défensif", +"defensiv", +"防御", +"防禦", +"defensivo", +"defensivo", +"защитная"), + +(1734, 'pet_stance_passive', "passive", 0, 0, +"수동적", +"passif", +"passiv", +"被动", +"被動", +"pasivo", +"pasivo", +"пассивная"), + +(1735, 'pet_stance_unknown', "unknown", 0, 0, +"알 수 없음", +"inconnu", +"unbekannt", +"未知", +"未知", +"desconocido", +"desconocido", +"неизвестная"); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES +('pet_usage_error', 100), +('pet_no_pet_error', 100), +('pet_stance_report', 100), +('pet_no_target_error', 100), +('pet_target_dead_error', 100), +('pet_invalid_target_error', 100), +('pet_pvp_prohibited_error', 100), +('pet_attack_success', 100), +('pet_attack_failed', 100), +('pet_follow_success', 100), +('pet_stay_success', 100), +('pet_unknown_command_error', 100), +('pet_stance_set_success', 100), +('pet_type_pet', 100), +('pet_type_guardian', 100), +('pet_stance_aggressive', 100), +('pet_stance_defensive', 100), +('pet_stance_passive', 100), +('pet_stance_unknown', 100); \ No newline at end of file diff --git a/src/strategy/actions/AttackAction.cpp b/src/strategy/actions/AttackAction.cpp index 4ea86943..38cb9683 100644 --- a/src/strategy/actions/AttackAction.cpp +++ b/src/strategy/actions/AttackAction.cpp @@ -84,9 +84,10 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/) return false; } - if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) || - sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId())) - && (target->IsPlayer() || target->IsPet())) + // Check if bot OR target is in prohibited zone/area + if ((target->IsPlayer() || target->IsPet()) && + (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) || + sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId()))) { if (verbose) botAI->TellError("I cannot attack other players in PvP prohibited areas."); diff --git a/src/strategy/actions/PetsAction.cpp b/src/strategy/actions/PetsAction.cpp index 945a8836..9e9e3172 100644 --- a/src/strategy/actions/PetsAction.cpp +++ b/src/strategy/actions/PetsAction.cpp @@ -25,7 +25,9 @@ bool PetsAction::Execute(Event event) if (param.empty()) { // If no parameter is provided, show usage instructions and return. - botAI->TellError("Usage: pet "); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_usage_error", "Usage: pet ", {}); + botAI->TellError(text); return false; } @@ -52,7 +54,9 @@ bool PetsAction::Execute(Event event) // If no pets or guardians are found, notify and return. if (targets.empty()) { - botAI->TellError("You have no pet or guardian pet."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_no_pet_error", "You have no pet or guardian pet.", {}); + botAI->TellError(text); return false; } @@ -63,42 +67,54 @@ bool PetsAction::Execute(Event event) if (param == "aggressive") { react = REACT_AGGRESSIVE; - stanceText = "aggressive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_aggressive", "aggressive", {}); } else if (param == "defensive") { react = REACT_DEFENSIVE; - stanceText = "defensive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_defensive", "defensive", {}); } else if (param == "passive") { react = REACT_PASSIVE; - stanceText = "passive"; + stanceText = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_passive", "passive", {}); } // The "stance" command simply reports the current stance of each pet/guardian. else if (param == "stance") { for (Creature* target : targets) { - std::string type = target->IsPet() ? "pet" : "guardian"; + std::string type = target->IsPet() ? + sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) : + sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {}); std::string name = target->GetName(); std::string stance; switch (target->GetReactState()) { case REACT_AGGRESSIVE: - stance = "aggressive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_aggressive", "aggressive", {}); break; case REACT_DEFENSIVE: - stance = "defensive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_defensive", "defensive", {}); break; case REACT_PASSIVE: - stance = "passive"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_passive", "passive", {}); break; default: - stance = "unknown"; + stance = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_unknown", "unknown", {}); break; } - botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + "."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_report", "Current stance of %type \"%name\": %stance.", + {{"type", type}, {"name", name}, {"stance", stance}}); + botAI->TellMaster(text); } return true; } @@ -121,17 +137,31 @@ bool PetsAction::Execute(Event event) // If no valid target is selected, show an error and return. if (!targetUnit) { - botAI->TellError("No valid target selected by master."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_no_target_error", "No valid target selected by master.", {}); + botAI->TellError(text); return false; } if (!targetUnit->IsAlive()) { - botAI->TellError("Target is not alive."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_target_dead_error", "Target is not alive.", {}); + botAI->TellError(text); return false; } if (!bot->IsValidAttackTarget(targetUnit)) { - botAI->TellError("Target is not a valid attack target for the bot."); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_invalid_target_error", "Target is not a valid attack target for the bot.", {}); + botAI->TellError(text); + return false; + } + if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) + && (targetUnit->IsPlayer() || targetUnit->IsPet())) + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {}); + botAI->TellError(text); return false; } @@ -182,9 +212,17 @@ bool PetsAction::Execute(Event event) } // Inform the master if the command succeeded or failed. if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to attack your target."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_attack_success", "Pet commanded to attack your target.", {}); + botAI->TellMaster(text); + } else if (!didAttack) - botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)"); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {}); + botAI->TellError(text); + } return didAttack; } // The "follow" command makes all pets/guardians follow the bot. @@ -192,7 +230,11 @@ bool PetsAction::Execute(Event event) { botAI->PetFollow(); if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to follow."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_follow_success", "Pet commanded to follow.", {}); + botAI->TellMaster(text); + } return true; } // The "stay" command causes all pets/guardians to stop and stay in place. @@ -229,14 +271,20 @@ bool PetsAction::Execute(Event event) } } if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet commanded to stay."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stay_success", "Pet commanded to stay.", {}); + botAI->TellMaster(text); + } return true; } // Unknown command: show usage instructions and return. else { - botAI->TellError("Unknown pet command: " + param + - ". Use: pet "); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_unknown_command_error", "Unknown pet command: %param. Use: pet ", + {{"param", param}}); + botAI->TellError(text); return false; } @@ -251,7 +299,12 @@ bool PetsAction::Execute(Event event) // Inform the master of the new stance if debug is enabled. if (sPlayerbotAIConfig->petChatCommandDebug == 1) - botAI->TellMaster("Pet stance set to " + stanceText + "."); + { + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "pet_stance_set_success", "Pet stance set to %stance.", + {{"stance", stanceText}}); + botAI->TellMaster(text); + } return true; -} +} \ No newline at end of file diff --git a/src/strategy/values/AttackersValue.cpp b/src/strategy/values/AttackersValue.cpp index 745369a3..d89f1f8d 100644 --- a/src/strategy/values/AttackersValue.cpp +++ b/src/strategy/values/AttackersValue.cpp @@ -107,7 +107,6 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set& t { ThreatMgr* threatMgr = ref->GetSource(); Unit* attacker = threatMgr->GetOwner(); - Unit* victim = attacker->GetVictim(); if (player->IsValidAttackTarget(attacker) && player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance) @@ -142,57 +141,107 @@ bool AttackersValue::hasRealThreat(Unit* attacker) (attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast(attacker)); } -bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range) +bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/) { - Creature* c = attacker->ToCreature(); - bool rti = false; - if (attacker && bot->GetGroup()) - rti = bot->GetGroup()->GetTargetIcon(7) == attacker->GetGUID(); - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI) + return false; - bool leaderHasThreat = false; - if (attacker && bot->GetGroup() && botAI->GetMaster()) - leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster()); - - bool isMemberBotGroup = false; - if (bot->GetGroup() && botAI->GetMaster()) - { - PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster()); - if (masterBotAI && !masterBotAI->IsRealPlayer()) - isMemberBotGroup = true; - } + // Basic check + if (!attacker) + return false; // bool inCannon = botAI->IsInVehicle(false, true); // bool enemy = botAI->GetAiObjectContext()->GetValue("enemy player target")->Get(); - return attacker && attacker->IsVisible() && attacker->IsInWorld() && attacker->GetMapId() == bot->GetMapId() && - !attacker->isDead() && - !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) && - // (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) && - // attacker->CanSeeOrDetect(bot) && - // !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) && - // !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/ - // attacker->isFeared()) && !rti) && - /*!sServerFacade->IsInRoots(attacker) &&*/ - !attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() && - // !(attacker->GetGUID().IsPet() && enemy) && - !(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) && - !attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) && - bot->CanSeeOrDetect(attacker) && - !(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) && - (attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet())) && - !(attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() && - (!bot->duel || bot->duel->Opponent != attacker)) && - (!c || - (!c->IsInEvadeMode() && - ((!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) || leaderHasThreat || - (!c->hasLootRecipient() && - (!c->GetVictim() || - (c->GetVictim() && - ((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) || - (botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) || - c->isTappedBy(bot)))); + // Validity checks + if (!attacker->IsVisible() || !attacker->IsInWorld() || attacker->GetMapId() != bot->GetMapId()) + return false; + + if (attacker->isDead() || attacker->HasSpiritOfRedemptionAura()) + return false; + + // Flag checks + if (attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2)) + return false; + + if (attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) + return false; + + // Relationship checks + if (attacker->IsFriendlyTo(bot)) + return false; + + // Critter exception + if (attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) + return false; + + // Visibility check + if (!bot->CanSeeOrDetect(attacker)) + return false; + + // PvP prohibition checks + if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) && + (sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) || + sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()))) + { + // This will stop aggresive pets from starting an attack. + // This will stop currently attacking pets from continuing their attack. + // This will first require the bot to change from a combat strat. It will + // not be reached if the bot only switches targets, including NPC targets. + for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); + itr != bot->m_Controlled.end(); ++itr) + { + Creature* creature = dynamic_cast(*itr); + if (creature && creature->GetVictim() == attacker) + { + creature->AttackStop(); + if (CharmInfo* charmInfo = creature->GetCharmInfo()) + charmInfo->SetIsCommandAttack(false); + } + } + + return false; + } + + // Unflagged player check + if (attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() && + (!bot->duel || bot->duel->Opponent != attacker)) + return false; + + // Creature-specific checks + Creature* c = attacker->ToCreature(); + if (c) + { + if (c->IsInEvadeMode()) + return false; + + bool leaderHasThreat = false; + if (bot->GetGroup() && botAI->GetMaster()) + leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster()); + + bool isMemberBotGroup = false; + if (bot->GetGroup() && botAI->GetMaster()) + { + PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster()); + if (masterBotAI && !masterBotAI->IsRealPlayer()) + isMemberBotGroup = true; + } + + bool canAttack = (!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) || + leaderHasThreat || + (!c->hasLootRecipient() && + (!c->GetVictim() || + (c->GetVictim() && + ((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) || + (botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) || + c->isTappedBy(bot); + + if (!canAttack) + return false; + } + + return true; } bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)