refactor(Core/UnitAI): Update target selector (#13169)
* refactor(Core/UnitAI): Update TargetSelector. Cherry-pick: *https://github.com/TrinityCore/TrinityCore/pull/19930 Co-authored-by: Treeston <treeston@users.noreply.github.com> * fix GGC/CLang errors in scripts * . * missing a file and fix a script error * . * fix path changes dude to a miss click in the second commit * . Co-authored-by: Maelthyrr <lynethris@protonmail.ch> Co-authored-by: Treeston <treeston@users.noreply.github.com>
This commit is contained in:
@@ -108,14 +108,14 @@ void UnitAI::DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, int32 aura)
|
Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
|
||||||
{
|
{
|
||||||
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura));
|
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, float dist, bool playerOnly, int32 aura)
|
void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
|
||||||
{
|
{
|
||||||
SelectTargetList(targetList, DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType);
|
SelectTargetList(targetList, num, targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
|
||||||
}
|
}
|
||||||
|
|
||||||
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
|
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
|
||||||
@@ -210,7 +210,7 @@ SpellCastResult UnitAI::DoCast(uint32 spellId)
|
|||||||
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER);
|
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER);
|
||||||
float range = spellInfo->GetMaxRange(false);
|
float range = spellInfo->GetMaxRange(false);
|
||||||
|
|
||||||
DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId);
|
DefaultTargetSelector targetSelector(me, range, playerOnly, true, -(int32)spellId);
|
||||||
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
|
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
|
||||||
&& targetSelector(me->GetVictim()))
|
&& targetSelector(me->GetVictim()))
|
||||||
target = me->GetVictim();
|
target = me->GetVictim();
|
||||||
@@ -317,6 +317,16 @@ void UnitAI::FillAISpellInfo()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThreatMgr& UnitAI::GetThreatMgr()
|
||||||
|
{
|
||||||
|
return me->GetThreatMgr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitAI::SortByDistance(std::list<Unit*>& list, bool ascending)
|
||||||
|
{
|
||||||
|
list.sort(Acore::ObjectDistanceOrderPred(me, ascending));
|
||||||
|
}
|
||||||
|
|
||||||
//Enable PlayerAI when charmed
|
//Enable PlayerAI when charmed
|
||||||
void PlayerAI::OnCharmed(bool apply)
|
void PlayerAI::OnCharmed(bool apply)
|
||||||
{
|
{
|
||||||
@@ -397,5 +407,8 @@ bool NonTankTargetSelector::operator()(Unit const* target) const
|
|||||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (Unit* currentVictim = _source->GetThreatMgr().GetCurrentVictim())
|
||||||
|
return target != currentVictim;
|
||||||
|
|
||||||
return target != _source->GetVictim();
|
return target != _source->GetVictim();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,14 +43,16 @@ struct DefaultTargetSelector : public Acore::unary_function<Unit*, bool>
|
|||||||
{
|
{
|
||||||
Unit const* me;
|
Unit const* me;
|
||||||
float m_dist;
|
float m_dist;
|
||||||
|
Unit const* except;
|
||||||
bool m_playerOnly;
|
bool m_playerOnly;
|
||||||
int32 m_aura;
|
int32 m_aura;
|
||||||
|
|
||||||
// unit: the reference unit
|
// unit: the reference unit
|
||||||
// dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
|
// dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
|
||||||
// playerOnly: self explaining
|
// playerOnly: self explaining
|
||||||
|
// withMainTank: allow current tank to be selected
|
||||||
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
|
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
|
||||||
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, int32 aura) : me(unit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) {}
|
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), except(withMainTank ? me->GetThreatMgr().GetCurrentVictim() : nullptr), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||||
|
|
||||||
bool operator()(Unit const* target) const
|
bool operator()(Unit const* target) const
|
||||||
{
|
{
|
||||||
@@ -60,6 +62,9 @@ struct DefaultTargetSelector : public Acore::unary_function<Unit*, bool>
|
|||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (target == except)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (m_playerOnly && (target->GetTypeId() != TYPEID_PLAYER))
|
if (m_playerOnly && (target->GetTypeId() != TYPEID_PLAYER))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -208,7 +213,7 @@ public:
|
|||||||
// - Not the current tank (if withTank = false)
|
// - Not the current tank (if withTank = false)
|
||||||
// - Has aura with ID <aura> (if aura > 0)
|
// - Has aura with ID <aura> (if aura > 0)
|
||||||
// - Does not have aura with ID -<aura> (if aura < 0)
|
// - Does not have aura with ID -<aura> (if aura < 0)
|
||||||
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0);
|
||||||
|
|
||||||
// Select the best target (in <targetType> order) satisfying <predicate> from the threat list.
|
// Select the best target (in <targetType> order) satisfying <predicate> from the threat list.
|
||||||
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
|
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
|
||||||
@@ -216,48 +221,30 @@ public:
|
|||||||
template <class PREDICATE>
|
template <class PREDICATE>
|
||||||
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
|
Unit* SelectTarget(SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
|
||||||
{
|
{
|
||||||
ThreatContainer::StorageType const& threatlist = me->GetThreatMgr().GetThreatList();
|
ThreatMgr& mgr = GetThreatMgr();
|
||||||
if (position >= threatlist.size())
|
// shortcut: if we ignore the first <offset> elements, and there are at most <offset> elements, then we ignore ALL elements
|
||||||
|
if (mgr.GetThreatListSize() <= position)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
std::list<Unit*> targetList;
|
std::list<Unit*> targetList;
|
||||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
SelectTargetList(targetList, mgr.GetThreatListSize(), targetType, position, predicate);
|
||||||
if (predicate((*itr)->getTarget()))
|
|
||||||
targetList.push_back((*itr)->getTarget());
|
|
||||||
|
|
||||||
if (position >= targetList.size())
|
// maybe nothing fulfills the predicate
|
||||||
|
if (targetList.empty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
|
|
||||||
targetList.sort(Acore::ObjectDistanceOrderPred(me));
|
|
||||||
|
|
||||||
switch (targetType)
|
switch (targetType)
|
||||||
{
|
{
|
||||||
case SelectTargetMethod::MaxDistance:
|
case SelectTargetMethod::MaxThreat:
|
||||||
case SelectTargetMethod::MaxThreat:
|
case SelectTargetMethod::MinThreat:
|
||||||
{
|
case SelectTargetMethod::MaxDistance:
|
||||||
std::list<Unit*>::iterator itr = targetList.begin();
|
case SelectTargetMethod::MinDistance:
|
||||||
std::advance(itr, position);
|
return targetList.front();
|
||||||
return *itr;
|
case SelectTargetMethod::Random:
|
||||||
}
|
return Acore::Containers::SelectRandomContainerElement(targetList);
|
||||||
case SelectTargetMethod::MinDistance:
|
default:
|
||||||
case SelectTargetMethod::MinThreat:
|
return nullptr;
|
||||||
{
|
|
||||||
std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
|
|
||||||
std::advance(ritr, position);
|
|
||||||
return *ritr;
|
|
||||||
}
|
|
||||||
case SelectTargetMethod::Random:
|
|
||||||
{
|
|
||||||
std::list<Unit*>::iterator itr = targetList.begin();
|
|
||||||
std::advance(itr, urand(position, targetList.size() - 1));
|
|
||||||
return *itr;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the best (up to) <num> targets (in <targetType> order) from the threat list that fulfill the following:
|
// Select the best (up to) <num> targets (in <targetType> order) from the threat list that fulfill the following:
|
||||||
@@ -270,35 +257,79 @@ public:
|
|||||||
// - Has aura with ID <aura> (if aura > 0)
|
// - Has aura with ID <aura> (if aura > 0)
|
||||||
// - Does not have aura with ID -<aura> (if aura < 0)
|
// - Does not have aura with ID -<aura> (if aura < 0)
|
||||||
// The resulting targets are stored in <targetList> (which is cleared first).
|
// The resulting targets are stored in <targetList> (which is cleared first).
|
||||||
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0);
|
||||||
|
|
||||||
// Select the best (up to) <num> targets (in <targetType> order) satisfying <predicate> from the threat list and stores them in <targetList> (which is cleared first).
|
// Select the best (up to) <num> targets (in <targetType> order) satisfying <predicate> from the threat list and stores them in <targetList> (which is cleared first).
|
||||||
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
|
// If <offset> is nonzero, the first <offset> entries in <targetType> order (or SelectTargetMethod::MaxThreat
|
||||||
// order, if <targetType> is SelectTargetMethod::Random) are skipped.
|
// order, if <targetType> is SelectTargetMethod::Random) are skipped.
|
||||||
template <class PREDICATE>
|
template <class PREDICATE>
|
||||||
void SelectTargetList(std::list<Unit*>& targetList, PREDICATE const& predicate, uint32 maxTargets, SelectTargetMethod targetType)
|
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate)
|
||||||
{
|
{
|
||||||
ThreatContainer::StorageType const& threatlist = me->GetThreatMgr().GetThreatList();
|
targetList.clear();
|
||||||
if (threatlist.empty())
|
ThreatMgr& mgr = GetThreatMgr();
|
||||||
return;
|
// shortcut: we're gonna ignore the first <offset> elements, and there's at most <offset> elements, so we ignore them all - nothing to do here
|
||||||
|
if (mgr.GetThreatListSize() <= position)
|
||||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
|
||||||
if (predicate((*itr)->getTarget()))
|
|
||||||
targetList.push_back((*itr)->getTarget());
|
|
||||||
|
|
||||||
if (targetList.size() < maxTargets)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
|
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
|
||||||
targetList.sort(Acore::ObjectDistanceOrderPred(me));
|
{
|
||||||
|
for (ThreatReference const* ref : mgr.GetUnsortedThreatList())
|
||||||
|
{
|
||||||
|
if (ref->IsOffline())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (targetType == SelectTargetMethod::MinDistance || targetType == SelectTargetMethod::MinThreat)
|
targetList.push_back(ref->GetVictim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Unit* currentVictim = mgr.GetCurrentVictim();
|
||||||
|
if (currentVictim)
|
||||||
|
targetList.push_back(currentVictim);
|
||||||
|
|
||||||
|
for (ThreatReference const* ref : mgr.GetSortedThreatList())
|
||||||
|
{
|
||||||
|
if (ref->IsOffline())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* thisTarget = ref->GetVictim();
|
||||||
|
if (thisTarget != currentVictim)
|
||||||
|
targetList.push_back(thisTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut: the list isn't gonna get any larger
|
||||||
|
if (targetList.size() <= position)
|
||||||
|
{
|
||||||
|
targetList.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// right now, list is unsorted for DISTANCE types - re-sort by SelectTargetMethod::MaxDistance
|
||||||
|
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
|
||||||
|
SortByDistance(targetList, targetType == SelectTargetMethod::MinDistance);
|
||||||
|
|
||||||
|
// now the list is MAX sorted, reverse for MIN types
|
||||||
|
if (targetType == SelectTargetMethod::MinThreat)
|
||||||
targetList.reverse();
|
targetList.reverse();
|
||||||
|
|
||||||
|
// ignore the first <offset> elements
|
||||||
|
while (position)
|
||||||
|
{
|
||||||
|
targetList.pop_front();
|
||||||
|
--position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then finally filter by predicate
|
||||||
|
targetList.remove_if([&predicate](Unit* target) { return !predicate(target); });
|
||||||
|
|
||||||
|
if (targetList.size() <= num)
|
||||||
|
return;
|
||||||
|
|
||||||
if (targetType == SelectTargetMethod::Random)
|
if (targetType == SelectTargetMethod::Random)
|
||||||
Acore::Containers::RandomResize(targetList, maxTargets);
|
Acore::Containers::RandomResize(targetList, num);
|
||||||
else
|
else
|
||||||
targetList.resize(maxTargets);
|
targetList.resize(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called at any Damage to any victim (before damage apply)
|
// Called at any Damage to any victim (before damage apply)
|
||||||
@@ -348,6 +379,10 @@ public:
|
|||||||
virtual void sOnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
|
virtual void sOnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
|
||||||
|
|
||||||
virtual std::string GetDebugInfo() const;
|
virtual std::string GetDebugInfo() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ThreatMgr& GetThreatMgr();
|
||||||
|
void SortByDistance(std::list<Unit*>& list, bool ascending = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlayerAI : public UnitAI
|
class PlayerAI : public UnitAI
|
||||||
|
|||||||
@@ -225,6 +225,9 @@ public:
|
|||||||
[[nodiscard]] bool isThreatListEmpty() const { return iThreatContainer.empty(); }
|
[[nodiscard]] bool isThreatListEmpty() const { return iThreatContainer.empty(); }
|
||||||
[[nodiscard]] bool areThreatListsEmpty() const { return iThreatContainer.empty() && iThreatOfflineContainer.empty(); }
|
[[nodiscard]] bool areThreatListsEmpty() const { return iThreatContainer.empty() && iThreatOfflineContainer.empty(); }
|
||||||
|
|
||||||
|
Acore::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.GetThreatList(); return { list.cbegin(), list.cend() }; }
|
||||||
|
Acore::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); }
|
||||||
|
|
||||||
void processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent);
|
void processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent);
|
||||||
|
|
||||||
bool isNeedUpdateToClient(uint32 time);
|
bool isNeedUpdateToClient(uint32 time);
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ public:
|
|||||||
}
|
}
|
||||||
case EVENT_IGNITE_MANA:
|
case EVENT_IGNITE_MANA:
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_IGNITE_MANA))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IGNITE_MANA))
|
||||||
{
|
{
|
||||||
DoCast(target, SPELL_IGNITE_MANA);
|
DoCast(target, SPELL_IGNITE_MANA);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,10 +94,10 @@ public:
|
|||||||
case EVENT_LAVA_BOMB_RANGED:
|
case EVENT_LAVA_BOMB_RANGED:
|
||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
SelectTargetList(targets, [this](Unit* target)
|
SelectTargetList(targets, 1, SelectTargetMethod::Random, 1, [this](Unit* target)
|
||||||
{
|
{
|
||||||
return target && target->IsPlayer() && target->GetDistance(me) > MELEE_TARGET_LOOKUP_DIST && target->GetDistance(me) < 100.0f;
|
return target && target->IsPlayer() && target->GetDistance(me) > MELEE_TARGET_LOOKUP_DIST && target->GetDistance(me) < 100.0f;
|
||||||
}, 1, SelectTargetMethod::Random);
|
});
|
||||||
|
|
||||||
if (!targets.empty())
|
if (!targets.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public:
|
|||||||
}
|
}
|
||||||
case EVENT_SHAZZRAH_CURSE:
|
case EVENT_SHAZZRAH_CURSE:
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_SHAZZRAH_CURSE))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHAZZRAH_CURSE))
|
||||||
{
|
{
|
||||||
DoCast(target, SPELL_SHAZZRAH_CURSE);
|
DoCast(target, SPELL_SHAZZRAH_CURSE);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -174,7 +174,7 @@ public:
|
|||||||
}
|
}
|
||||||
case EVENT_SHADOW_WORD_PAIN:
|
case EVENT_SHADOW_WORD_PAIN:
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_SHADOW_WORD_PAIN))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_SHADOW_WORD_PAIN))
|
||||||
{
|
{
|
||||||
DoCast(target, SPELL_SHADOW_WORD_PAIN);
|
DoCast(target, SPELL_SHADOW_WORD_PAIN);
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ public:
|
|||||||
}
|
}
|
||||||
case EVENT_IMMOLATE:
|
case EVENT_IMMOLATE:
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_IMMOLATE))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_IMMOLATE))
|
||||||
{
|
{
|
||||||
DoCast(target, SPELL_IMMOLATE);
|
DoCast(target, SPELL_IMMOLATE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public:
|
|||||||
events.ScheduleEvent(EVENT_SPELL_STOMP, 30000);
|
events.ScheduleEvent(EVENT_SPELL_STOMP, 30000);
|
||||||
break;
|
break;
|
||||||
case EVENT_SPELL_BURN:
|
case EVENT_SPELL_BURN:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true, -SPELL_BURN_DAMAGE))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true, true, -SPELL_BURN_DAMAGE))
|
||||||
me->CastSpell(target, SPELL_BURN, false);
|
me->CastSpell(target, SPELL_BURN, false);
|
||||||
events.ScheduleEvent(EVENT_SPELL_BURN, 60000);
|
events.ScheduleEvent(EVENT_SPELL_BURN, 60000);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ public:
|
|||||||
break;
|
break;
|
||||||
case EVENT_MARK_OF_ARLOKK:
|
case EVENT_MARK_OF_ARLOKK:
|
||||||
{
|
{
|
||||||
Unit* target = SelectTarget(SelectTargetMethod::MaxThreat, urand(1, 3), 0.0f, false, -SPELL_MARK_OF_ARLOKK);
|
Unit* target = SelectTarget(SelectTargetMethod::MaxThreat, urand(1, 3), 0.0f, false, true, -SPELL_MARK_OF_ARLOKK);
|
||||||
if (!target)
|
if (!target)
|
||||||
target = me->GetVictim();
|
target = me->GetVictim();
|
||||||
if (target)
|
if (target)
|
||||||
|
|||||||
@@ -1768,7 +1768,7 @@ struct npc_coren_direbrew_sisters : public ScriptedAI
|
|||||||
})
|
})
|
||||||
.Schedule(Seconds(2), [this](TaskContext mugChuck)
|
.Schedule(Seconds(2), [this](TaskContext mugChuck)
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, false, -SPELL_HAS_DARK_BREWMAIDENS_BREW))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, false, true, -SPELL_HAS_DARK_BREWMAIDENS_BREW))
|
||||||
{
|
{
|
||||||
DoCast(target, SPELL_CHUCK_MUG);
|
DoCast(target, SPELL_CHUCK_MUG);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,10 +105,10 @@ struct npc_obsidian_destroyer : public ScriptedAI
|
|||||||
_scheduler.Schedule(6s, [this](TaskContext context)
|
_scheduler.Schedule(6s, [this](TaskContext context)
|
||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
SelectTargetList(targets, [&](Unit* target)
|
SelectTargetList(targets, 6, SelectTargetMethod::Random, 1, [&](Unit* target)
|
||||||
{
|
{
|
||||||
return target && target->IsPlayer() && target->GetPower(POWER_MANA) > 0;
|
return target && target->IsPlayer() && target->GetPower(POWER_MANA) > 0;
|
||||||
}, 6, SelectTargetMethod::Random);
|
});
|
||||||
|
|
||||||
for (Unit* target : targets)
|
for (Unit* target : targets)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ struct boss_amanitar : public BossAI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_MINI))
|
if (SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true, -SPELL_MINI))
|
||||||
{
|
{
|
||||||
DoCastSelf(SPELL_REMOVE_MUSHROOM_POWER, true);
|
DoCastSelf(SPELL_REMOVE_MUSHROOM_POWER, true);
|
||||||
DoCastAOE(SPELL_MINI);
|
DoCastAOE(SPELL_MINI);
|
||||||
|
|||||||
+2
-2
@@ -224,7 +224,7 @@ public:
|
|||||||
break;
|
break;
|
||||||
case EVENT_ENERVATING_BRAND:
|
case EVENT_ENERVATING_BRAND:
|
||||||
for (uint8 i = 0; i < RAID_MODE<uint8>(2, 4, 2, 4); i++)
|
for (uint8 i = 0; i < RAID_MODE<uint8>(2, 4, 2, 4); i++)
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true, -SPELL_ENERVATING_BRAND))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true, true, -SPELL_ENERVATING_BRAND))
|
||||||
me->CastSpell(target, SPELL_ENERVATING_BRAND, true);
|
me->CastSpell(target, SPELL_ENERVATING_BRAND, true);
|
||||||
events.ScheduleEvent(EVENT_ENERVATING_BRAND, 26000);
|
events.ScheduleEvent(EVENT_ENERVATING_BRAND, 26000);
|
||||||
break;
|
break;
|
||||||
@@ -311,7 +311,7 @@ public:
|
|||||||
break;
|
break;
|
||||||
case EVENT_ENERVATING_BRAND:
|
case EVENT_ENERVATING_BRAND:
|
||||||
for (uint8 i = 0; i < RAID_MODE<uint8>(4, 10, 4, 10); i++)
|
for (uint8 i = 0; i < RAID_MODE<uint8>(4, 10, 4, 10); i++)
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true, -SPELL_ENERVATING_BRAND))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f, true, true, -SPELL_ENERVATING_BRAND))
|
||||||
me->CastSpell(target, SPELL_ENERVATING_BRAND, true);
|
me->CastSpell(target, SPELL_ENERVATING_BRAND, true);
|
||||||
_events.ScheduleEvent(EVENT_ENERVATING_BRAND, 26000);
|
_events.ScheduleEvent(EVENT_ENERVATING_BRAND, 26000);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ public:
|
|||||||
events.ScheduleEvent(EVENT_METEOR_STRIKE, 40000);
|
events.ScheduleEvent(EVENT_METEOR_STRIKE, 40000);
|
||||||
break;
|
break;
|
||||||
case EVENT_FIERY_COMBUSTION:
|
case EVENT_FIERY_COMBUSTION:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, -SPELL_TWILIGHT_REALM))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, true, -SPELL_TWILIGHT_REALM))
|
||||||
me->CastSpell(target, SPELL_FIERY_COMBUSTION, false);
|
me->CastSpell(target, SPELL_FIERY_COMBUSTION, false);
|
||||||
events.ScheduleEvent(EVENT_FIERY_COMBUSTION, 25000);
|
events.ScheduleEvent(EVENT_FIERY_COMBUSTION, 25000);
|
||||||
break;
|
break;
|
||||||
@@ -542,7 +542,7 @@ public:
|
|||||||
_events.ScheduleEvent(EVENT_BREATH, urand(10000, 12000));
|
_events.ScheduleEvent(EVENT_BREATH, urand(10000, 12000));
|
||||||
break;
|
break;
|
||||||
case EVENT_SOUL_CONSUMPTION:
|
case EVENT_SOUL_CONSUMPTION:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, SPELL_TWILIGHT_REALM))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, true, SPELL_TWILIGHT_REALM))
|
||||||
me->CastSpell(target, SPELL_SOUL_CONSUMPTION, false);
|
me->CastSpell(target, SPELL_SOUL_CONSUMPTION, false);
|
||||||
_events.ScheduleEvent(EVENT_SOUL_CONSUMPTION, 20000);
|
_events.ScheduleEvent(EVENT_SOUL_CONSUMPTION, 20000);
|
||||||
break;
|
break;
|
||||||
|
|||||||
+3
-3
@@ -211,12 +211,12 @@ public:
|
|||||||
events.RepeatEvent(urand(10000, 15000));
|
events.RepeatEvent(urand(10000, 15000));
|
||||||
break;
|
break;
|
||||||
case EVENT_SPELL_FEL_LIGHTNING:
|
case EVENT_SPELL_FEL_LIGHTNING:
|
||||||
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true) )
|
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true) )
|
||||||
me->CastSpell(target, SPELL_FEL_LIGHTNING, false);
|
me->CastSpell(target, SPELL_FEL_LIGHTNING, false);
|
||||||
events.RepeatEvent(urand(10000, 15000));
|
events.RepeatEvent(urand(10000, 15000));
|
||||||
break;
|
break;
|
||||||
case EVENT_SPELL_INCINERATE_FLESH:
|
case EVENT_SPELL_INCINERATE_FLESH:
|
||||||
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true) )
|
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true) )
|
||||||
{
|
{
|
||||||
Talk(EMOTE_INCINERATE, target);
|
Talk(EMOTE_INCINERATE, target);
|
||||||
Talk(SAY_INCINERATE);
|
Talk(SAY_INCINERATE);
|
||||||
@@ -230,7 +230,7 @@ public:
|
|||||||
events.RepeatEvent(urand(25000, 45000));
|
events.RepeatEvent(urand(25000, 45000));
|
||||||
break;
|
break;
|
||||||
case EVENT_SPELL_LEGION_FLAME:
|
case EVENT_SPELL_LEGION_FLAME:
|
||||||
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true) )
|
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true) )
|
||||||
{
|
{
|
||||||
Talk(EMOTE_LEGION_FLAME, target);
|
Talk(EMOTE_LEGION_FLAME, target);
|
||||||
me->CastSpell(target, SPELL_LEGION_FLAME, false);
|
me->CastSpell(target, SPELL_LEGION_FLAME, false);
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ public:
|
|||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case ACTION_MARK_OF_THE_FALLEN_CHAMPION:
|
case ACTION_MARK_OF_THE_FALLEN_CHAMPION:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, -SPELL_MARK_OF_THE_FALLEN_CHAMPION))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, true, -SPELL_MARK_OF_THE_FALLEN_CHAMPION))
|
||||||
{
|
{
|
||||||
++_fallenChampionCastCount;
|
++_fallenChampionCastCount;
|
||||||
me->CastSpell(target, SPELL_MARK_OF_THE_FALLEN_CHAMPION, false);
|
me->CastSpell(target, SPELL_MARK_OF_THE_FALLEN_CHAMPION, false);
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ public:
|
|||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
uint32 minTargets = RAID_MODE<uint32>(3, 8, 3, 8);
|
uint32 minTargets = RAID_MODE<uint32>(3, 8, 3, 8);
|
||||||
SelectTargetList(targets, minTargets, SelectTargetMethod::Random, -5.0f, true);
|
SelectTargetList(targets, minTargets, SelectTargetMethod::Random, 0, -5.0f, true);
|
||||||
float minDist = 0.0f;
|
float minDist = 0.0f;
|
||||||
if (targets.size() >= minTargets)
|
if (targets.size() >= minTargets)
|
||||||
minDist = -5.0f;
|
minDist = -5.0f;
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ public:
|
|||||||
count = 3;
|
count = 3;
|
||||||
|
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
SelectTargetList(targets, NonTankTargetSelector(me, true), count, SelectTargetMethod::Random);
|
SelectTargetList(targets, count, SelectTargetMethod::Random, 0, NonTankTargetSelector(me, true));
|
||||||
if (!targets.empty())
|
if (!targets.empty())
|
||||||
for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
|
for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
|
||||||
me->CastSpell(*itr, SPELL_SUMMON_SHADE, true);
|
me->CastSpell(*itr, SPELL_SUMMON_SHADE, true);
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ public:
|
|||||||
void SelectTarget(std::list<WorldObject*>& targets)
|
void SelectTarget(std::list<WorldObject*>& targets)
|
||||||
{
|
{
|
||||||
targets.clear();
|
targets.clear();
|
||||||
Unit* target = GetCaster()->GetAI()->SelectTarget(SelectTargetMethod::Random, 1, -1.0f, true, -SPELL_IMPALED); // -1.0f as it takes into account object size
|
Unit* target = GetCaster()->GetAI()->SelectTarget(SelectTargetMethod::Random, 1, -1.0f, true,true, -SPELL_IMPALED); // -1.0f as it takes into account object size
|
||||||
if (!target)
|
if (!target)
|
||||||
target = GetCaster()->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true); // if only tank or noone outside of boss' model
|
target = GetCaster()->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true); // if only tank or noone outside of boss' model
|
||||||
if (!target)
|
if (!target)
|
||||||
|
|||||||
@@ -548,7 +548,7 @@ public:
|
|||||||
case EVENT_SLIME_PUDDLE:
|
case EVENT_SLIME_PUDDLE:
|
||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
SelectTargetList(targets, 2, SelectTargetMethod::Random, 0.0f, true);
|
SelectTargetList(targets, 2, SelectTargetMethod::Random, 0, 0.0f, true);
|
||||||
if (!targets.empty())
|
if (!targets.empty())
|
||||||
for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
|
for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
|
||||||
me->CastSpell(*itr, SPELL_SLIME_PUDDLE_TRIGGER, true);
|
me->CastSpell(*itr, SPELL_SLIME_PUDDLE_TRIGGER, true);
|
||||||
@@ -634,7 +634,7 @@ public:
|
|||||||
if (Is25ManRaid())
|
if (Is25ManRaid())
|
||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
SelectTargetList(targets, MalleableGooSelector(me), (IsHeroic() ? 3 : 2), SelectTargetMethod::Random);
|
SelectTargetList(targets, (IsHeroic() ? 3 : 2), SelectTargetMethod::Random, 0, MalleableGooSelector(me));
|
||||||
|
|
||||||
if (!targets.empty())
|
if (!targets.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ public:
|
|||||||
{
|
{
|
||||||
std::list<Unit*> targets;
|
std::list<Unit*> targets;
|
||||||
uint32 minTargets = RAID_MODE<uint32>(3, 8, 3, 8);
|
uint32 minTargets = RAID_MODE<uint32>(3, 8, 3, 8);
|
||||||
SelectTargetList(targets, minTargets, SelectTargetMethod::Random, -5.0f, true);
|
SelectTargetList(targets, minTargets, SelectTargetMethod::Random, 0, -5.0f, true);
|
||||||
float minDist = 0.0f;
|
float minDist = 0.0f;
|
||||||
if (targets.size() >= minTargets)
|
if (targets.size() >= minTargets)
|
||||||
minDist = -5.0f;
|
minDist = -5.0f;
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ public:
|
|||||||
switch (eventId)
|
switch (eventId)
|
||||||
{
|
{
|
||||||
case EVENT_DEATH_PLAGUE:
|
case EVENT_DEATH_PLAGUE:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, -SPELL_RECENTLY_INFECTED))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, true, -SPELL_RECENTLY_INFECTED))
|
||||||
{
|
{
|
||||||
Talk(EMOTE_DEATH_PLAGUE_WARNING, target);
|
Talk(EMOTE_DEATH_PLAGUE_WARNING, target);
|
||||||
DoCast(target, SPELL_DEATH_PLAGUE);
|
DoCast(target, SPELL_DEATH_PLAGUE);
|
||||||
@@ -1175,7 +1175,7 @@ public:
|
|||||||
Talk(SAY_SVALNA_AGGRO);
|
Talk(SAY_SVALNA_AGGRO);
|
||||||
break;
|
break;
|
||||||
case EVENT_IMPALING_SPEAR:
|
case EVENT_IMPALING_SPEAR:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, -SPELL_IMPALING_SPEAR))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, true, -SPELL_IMPALING_SPEAR))
|
||||||
{
|
{
|
||||||
DoCast(me, SPELL_AETHER_SHIELD);
|
DoCast(me, SPELL_AETHER_SHIELD);
|
||||||
me->AddAura(70203, me);
|
me->AddAura(70203, me);
|
||||||
@@ -1361,7 +1361,7 @@ public:
|
|||||||
Events.ScheduleEvent(EVENT_ARNATH_SMITE, urand(4000, 7000));
|
Events.ScheduleEvent(EVENT_ARNATH_SMITE, urand(4000, 7000));
|
||||||
break;
|
break;
|
||||||
case EVENT_ARNATH_DOMINATE_MIND:
|
case EVENT_ARNATH_DOMINATE_MIND:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, -SPELL_DOMINATE_MIND))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0.0f, true, true, -SPELL_DOMINATE_MIND))
|
||||||
DoCast(target, SPELL_DOMINATE_MIND);
|
DoCast(target, SPELL_DOMINATE_MIND);
|
||||||
Events.ScheduleEvent(EVENT_ARNATH_DOMINATE_MIND, urand(28000, 37000));
|
Events.ScheduleEvent(EVENT_ARNATH_DOMINATE_MIND, urand(28000, 37000));
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public:
|
|||||||
events.RepeatEvent(20000);
|
events.RepeatEvent(20000);
|
||||||
break;
|
break;
|
||||||
case EVENT_MUTATING_INJECTION:
|
case EVENT_MUTATING_INJECTION:
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, -SPELL_MUTATING_INJECTION))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, true, -SPELL_MUTATING_INJECTION))
|
||||||
{
|
{
|
||||||
me->CastSpell(target, SPELL_MUTATING_INJECTION, false);
|
me->CastSpell(target, SPELL_MUTATING_INJECTION, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ public:
|
|||||||
case EVENT_CHAINS:
|
case EVENT_CHAINS:
|
||||||
for (uint8 i = 0; i < 3; ++i)
|
for (uint8 i = 0; i < 3; ++i)
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 200, true, -SPELL_CHAINS_OF_KELTHUZAD))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 200, true, true, -SPELL_CHAINS_OF_KELTHUZAD))
|
||||||
{
|
{
|
||||||
me->CastSpell(target, SPELL_CHAINS_OF_KELTHUZAD, true);
|
me->CastSpell(target, SPELL_CHAINS_OF_KELTHUZAD, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public:
|
|||||||
Talk(EMOTE_WEB_WRAP);
|
Talk(EMOTE_WEB_WRAP);
|
||||||
for (uint8 i = 0; i < RAID_MODE(1, 2); ++i)
|
for (uint8 i = 0; i < RAID_MODE(1, 2); ++i)
|
||||||
{
|
{
|
||||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0, true, -SPELL_WEB_WRAP))
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 0, true, true, -SPELL_WEB_WRAP))
|
||||||
{
|
{
|
||||||
target->RemoveAura(RAID_MODE(SPELL_WEB_SPRAY_10, SPELL_WEB_SPRAY_25));
|
target->RemoveAura(RAID_MODE(SPELL_WEB_SPRAY_10, SPELL_WEB_SPRAY_25));
|
||||||
uint8 pos = urand(0, 2);
|
uint8 pos = urand(0, 2);
|
||||||
|
|||||||
@@ -1505,7 +1505,7 @@ public:
|
|||||||
{
|
{
|
||||||
uint8 i = 0;
|
uint8 i = 0;
|
||||||
std::list<Unit*> drakes;
|
std::list<Unit*> drakes;
|
||||||
c->AI()->SelectTargetList(drakes, (c->GetMap()->GetSpawnMode() == 0 ? 1 : 3), SelectTargetMethod::Random, 0.0f, false, 57403 /*only drakes have this aura*/);
|
c->AI()->SelectTargetList(drakes, (c->GetMap()->GetSpawnMode() == 0 ? 1 : 3), SelectTargetMethod::Random, 0, 0.0f, false, true, 57403 /*only drakes have this aura*/);
|
||||||
for (std::list<Unit*>::iterator itr = drakes.begin(); itr != drakes.end() && i < 3; ++itr)
|
for (std::list<Unit*>::iterator itr = drakes.begin(); itr != drakes.end() && i < 3; ++itr)
|
||||||
{
|
{
|
||||||
DrakeGUID[i++] = (*itr)->GetGUID();
|
DrakeGUID[i++] = (*itr)->GetGUID();
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ public:
|
|||||||
events.RepeatEvent(urand(4000, 5000));
|
events.RepeatEvent(urand(4000, 5000));
|
||||||
break;
|
break;
|
||||||
case EVENT_FROST_TOMB:
|
case EVENT_FROST_TOMB:
|
||||||
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true) )
|
if( Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, true) )
|
||||||
if( !target->HasAura(SPELL_FROST_TOMB_AURA) )
|
if( !target->HasAura(SPELL_FROST_TOMB_AURA) )
|
||||||
{
|
{
|
||||||
Talk(SAY_FROST_TOMB_EMOTE, target);
|
Talk(SAY_FROST_TOMB_EMOTE, target);
|
||||||
|
|||||||
Reference in New Issue
Block a user