Files
mod-playerbots/src/strategy/values/AttackersValue.cpp
NoxMax dde16674c3 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.
2025-12-08 12:34:16 +01:00

288 lines
9.4 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackersValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
#include "ReputationMgr.h"
#include "ServerFacade.h"
GuidVector AttackersValue::Calculate()
{
std::unordered_set<Unit*> targets;
GuidVector result;
if (!botAI->AllowActivity(ALL_ACTIVITY))
return result;
AddAttackersOf(bot, targets);
if (Group* group = bot->GetGroup())
AddAttackersOf(group, targets);
RemoveNonThreating(targets);
// prioritized target
GuidVector prioritizedTargets = AI_VALUE(GuidVector, "prioritized targets");
for (ObjectGuid target : prioritizedTargets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && IsValidTarget(unit, bot))
{
targets.insert(unit);
}
}
if (Group* group = bot->GetGroup())
{
ObjectGuid skullGuid = group->GetTargetIcon(7);
Unit* skullTarget = botAI->GetUnit(skullGuid);
if (skullTarget && IsValidTarget(skullTarget, bot))
{
targets.insert(skullTarget);
}
}
for (Unit* unit : targets)
result.push_back(unit->GetGUID());
if (bot->duel && bot->duel->Opponent)
result.push_back(bot->duel->Opponent->GetGUID());
// workaround for bots of same faction not fighting in arena
if (bot->InArena())
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible targets");
for (ObjectGuid const guid : possibleTargets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
{
result.push_back(unit->GetGUID());
}
}
}
return result;
}
void AttackersValue::AddAttackersOf(Group* group, std::unordered_set<Unit*>& targets)
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member == bot || member->GetMapId() != bot->GetMapId() ||
sServerFacade->GetDistance2d(bot, member) > sPlayerbotAIConfig->sightDistance)
continue;
AddAttackersOf(member, targets);
}
}
struct AddGuardiansHelper
{
explicit AddGuardiansHelper(std::vector<Unit*>& units) : units(units) {}
void operator()(Unit* target) const { units.push_back(target); }
std::vector<Unit*>& units;
};
void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& targets)
{
if (!player || !player->IsInWorld() || player->IsBeingTeleported())
return;
HostileRefMgr& refManager = player->getHostileRefMgr();
HostileReference* ref = refManager.getFirst();
if (!ref)
return;
while (ref)
{
ThreatMgr* threatMgr = ref->GetSource();
Unit* attacker = threatMgr->GetOwner();
if (player->IsValidAttackTarget(attacker) &&
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
{
targets.insert(attacker);
}
ref = ref->next();
}
}
void AttackersValue::RemoveNonThreating(std::unordered_set<Unit*>& targets)
{
for (std::unordered_set<Unit*>::iterator tIter = targets.begin(); tIter != targets.end();)
{
Unit* unit = *tIter;
if (bot->GetMapId() != unit->GetMapId() || !hasRealThreat(unit) || !IsValidTarget(unit, bot))
{
std::unordered_set<Unit*>::iterator tIter2 = tIter;
++tIter;
targets.erase(tIter2);
}
else
++tIter;
}
}
bool AttackersValue::hasRealThreat(Unit* attacker)
{
return attacker && attacker->IsInWorld() && attacker->IsAlive() && !attacker->IsPolymorphed() &&
// !attacker->isInRoots() &&
!attacker->IsFriendlyTo(bot);
(attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker));
}
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return false;
// Basic check
if (!attacker)
return false;
// bool inCannon = botAI->IsInVehicle(false, true);
// bool enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get();
// 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<Creature*>(*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)
{
return IsPossibleTarget(attacker, bot) && bot->IsWithinLOSInMap(attacker);
// (attacker->GetThreatMgr().getCurrentVictim() || attacker->GetGuidValue(UNIT_FIELD_TARGET) ||
// attacker->GetGUID().IsPlayer() || attacker->GetGUID() ==
// GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<ObjectGuid>("pull target")->Get());
}
bool PossibleAddsValue::Calculate()
{
GuidVector possible = botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los")->Get();
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : possible)
{
if (find(attackers.begin(), attackers.end(), guid) != attackers.end())
continue;
if (Unit* add = botAI->GetUnit(guid))
{
if (!add->GetTarget() && !add->GetThreatMgr().getCurrentVictim() && add->IsHostileTo(bot))
{
for (ObjectGuid const attackerGUID : attackers)
{
Unit* attacker = botAI->GetUnit(attackerGUID);
if (!attacker)
continue;
float dist = sServerFacade->GetDistance2d(attacker, add);
if (sServerFacade->IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig->aoeRadius * 1.5f))
continue;
if (sServerFacade->IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig->aggroDistance))
return true;
}
}
}
}
return false;
}