Core/Spells: Simplify SortTargetsWithPriorityRules

* Remove manul weight assignment
* Removed std::vector alloc
This commit is contained in:
Shauren
2025-09-06 12:28:12 +02:00
parent 2e3f3fda3f
commit 03d072da46
3 changed files with 55 additions and 59 deletions

View File

@@ -9633,67 +9633,41 @@ void SelectRandomInjuredTargets(std::list<WorldObject*>& targets, size_t maxTarg
std::ranges::transform(tempTargets.begin(), tempTargets.begin() + maxTargets, targets.begin(), Trinity::TupleElement<0>);
}
void SortTargetsWithPriorityRules(std::list<WorldObject*>& targets, size_t maxTargets, std::vector<PriorityRules> const& rules)
void SortTargetsWithPriorityRules(std::list<WorldObject*>& targets, size_t maxTargets, std::span<TargetPriorityRule const> rules)
{
if (targets.size() <= maxTargets)
return;
std::vector<std::pair<WorldObject*, int32>> prioritizedTargets;
std::vector<std::pair<WorldObject*, int32>> prioritizedTargets(targets.size());
// score each target based on how many rules they satisfy.
for (WorldObject* object : targets)
std::ranges::transform(targets, prioritizedTargets.begin(), [&](WorldObject* target)
{
Unit* unit = object ? object->ToUnit() : nullptr;
if (!unit)
continue;
int32 score = 0;
for (std::size_t i = 0; i < rules.size(); ++i)
if (rules[i].Rule(target))
score |= 1 << (rules.size() - 1 - i);
for (PriorityRules const& rule : rules)
{
if (rule.condition(unit))
score += rule.weight;
}
prioritizedTargets.emplace_back(object, score);
}
// the higher the value, the higher the priority is.
std::ranges::sort(prioritizedTargets, [](const std::pair<WorldObject*, int32>& left, const std::pair<WorldObject*, int32>& right)
{
return left.second > right.second;
return std::make_pair(target, score);
});
size_t cutOff = std::min(maxTargets, prioritizedTargets.size());
// the higher the value, the higher the priority is.
std::ranges::sort(prioritizedTargets, std::ranges::greater(), Trinity::TupleElement<1>);
int32 tieScore = prioritizedTargets[maxTargets - 1].second;
// if there are ties at the cutoff, shuffle them to avoid selection bias.
if (cutOff < prioritizedTargets.size())
if (prioritizedTargets[maxTargets].second == tieScore)
{
int32 tieScore = prioritizedTargets[cutOff - 1].second;
auto const isTieScore = [tieScore](const std::pair<WorldObject*, int32>& entry)
{ return entry.second == tieScore; };
// scan backwards to include tied entries before the cutoff.
std::ptrdiff_t tieStart = static_cast<std::ptrdiff_t>(cutOff - 1);
while (tieStart > 0 && isTieScore(prioritizedTargets[tieStart - 1]))
--tieStart;
// scan forward to include tied entries after the cutoff.
std::ptrdiff_t tieEnd = static_cast<std::ptrdiff_t>(cutOff);
while (tieEnd < static_cast<std::ptrdiff_t>(prioritizedTargets.size()) && isTieScore(prioritizedTargets[tieEnd]))
++tieEnd;
auto toShuffle = std::equal_range(prioritizedTargets.begin(), prioritizedTargets.end(), std::pair<WorldObject*, int32>(nullptr, tieScore),
[](std::pair<WorldObject*, int32> const& target1, std::pair<WorldObject*, int32> const& target2) { return target1.second > target2.second; });
// shuffle only the tied range to randomize final selection.
Containers::RandomShuffle(
std::ranges::next(prioritizedTargets.begin(), tieStart),
std::ranges::next(prioritizedTargets.begin(), tieEnd));
Containers::RandomShuffle(toShuffle.first, toShuffle.second);
}
targets.clear();
for (size_t i = 0; i < cutOff; ++i)
targets.push_back(prioritizedTargets[i].first);
targets.resize(maxTargets);
std::ranges::transform(prioritizedTargets.begin(), prioritizedTargets.begin() + maxTargets, targets.begin(), Trinity::TupleElement<0>);
}
} //namespace Trinity

View File

@@ -18,6 +18,7 @@
#ifndef __SPELL_H
#define __SPELL_H
#include "Concepts.h"
#include "ConditionMgr.h"
#include "DBCEnums.h"
#include "Duration.h"
@@ -27,6 +28,7 @@
#include "Position.h"
#include "SharedDefines.h"
#include "SpellDefines.h"
#include "Types.h"
#include "UniqueTrackablePtr.h"
#include <memory>
@@ -1055,15 +1057,35 @@ namespace Trinity
TC_GAME_API void SelectRandomInjuredTargets(std::list<WorldObject*>& targets, size_t maxTargets, bool prioritizePlayers, Unit const* prioritizeGroupMembersOf = nullptr);
struct PriorityRules
struct TargetPriorityRule
{
int32 weight;
std::function<bool(Unit*)> condition;
template <typename Func> requires (!std::same_as<Func, TargetPriorityRule>)
TargetPriorityRule(Func&& func) : Rule([func = std::forward<Func>(func)]<typename T = WorldObject /*template to avoid Object.h dependency*/>(T* target)
{
if constexpr (invocable_r<Func, bool, WorldObject*>)
return std::invoke(func, target);
else if constexpr (invocable_r<Func, bool, Unit*>)
return target->IsUnit() && std::invoke(func, target->ToUnit());
else if constexpr (invocable_r<Func, bool, Player*>)
return target->IsPlayer() && std::invoke(func, target->ToPlayer());
else
static_assert(dependant_false_v<T>, "Unsupported object type, use WorldObject* as your rule argument");
})
{
}
std::function<bool(WorldObject*)> Rule;
};
inline std::vector<PriorityRules> CreatePriorityRules(std::initializer_list<PriorityRules> rules) { return { rules }; }
TC_GAME_API void SortTargetsWithPriorityRules(std::list<WorldObject*>& targets, size_t maxTargets, std::span<TargetPriorityRule const> rules);
TC_GAME_API void SortTargetsWithPriorityRules(std::list<WorldObject*>& targets, size_t maxTargets, std::vector<PriorityRules> const& rules);
template <std::size_t N>
inline void SortTargetsWithPriorityRules(std::list<WorldObject*>& targets, size_t maxTargets, std::array<TargetPriorityRule, N> const& rules)
{
static_assert(N <= 31);
SortTargetsWithPriorityRules(targets, maxTargets, std::span(rules));
}
}
extern template void Spell::SearchTargets<Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck>>(Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck>& searcher, uint32 containerMask, WorldObject* referer, Position const* pos, float radius);

View File

@@ -2119,9 +2119,9 @@ class spell_pri_power_word_radiance : public SpellScript
Unit* explTarget = GetExplTargetUnit();
// we must add one since explicit target is always chosen.
uint32 maxTargets = GetEffectInfo(EFFECT_2).CalcValue(GetCaster()) + 1;
uint32 maxTargets = GetEffectInfo(EFFECT_2).CalcValue(caster) + 1;
SortTargetsWithPriorityRules(targets, maxTargets, GetRadianceRules(caster, explTarget));
Trinity::SortTargetsWithPriorityRules(targets, maxTargets, GetRadianceRules(caster, explTarget));
for (WorldObject const* target : targets)
{
@@ -2139,16 +2139,16 @@ class spell_pri_power_word_radiance : public SpellScript
GetHitUnit()->SendPlaySpellVisual(target, SPELL_VISUAL_PRIEST_POWER_WORD_RADIANCE, 0, 0, 70.0f);
}
static std::vector<Trinity::PriorityRules> GetRadianceRules(Unit const* caster, Unit const* explTarget)
static std::array<Trinity::TargetPriorityRule, 5> GetRadianceRules(Unit const* caster, Unit const* explTarget)
{
return Trinity::CreatePriorityRules
({
{ .weight = 1, .condition = [caster](Unit const* target) { return target->IsInRaidWith(caster); }},
{ .weight = 2, .condition = [](Unit const* target) { return target->IsPlayer() || (target->IsCreature() && target->ToCreature()->IsTreatedAsRaidUnit()); }},
{ .weight = 4, .condition = [](Unit const* target) { return !target->IsFullHealth(); }},
{ .weight = 8, .condition = [caster](Unit const* target) { return !target->HasAura(SPELL_PRIEST_ATONEMENT_EFFECT, caster->GetGUID()); }},
{ .weight = 16, .condition = [explTarget](Unit const* target) { return target == explTarget; }}
});
return
{
[explTarget](WorldObject const* target) { return target == explTarget; },
[caster](Unit const* target) { return !target->HasAura(SPELL_PRIEST_ATONEMENT_EFFECT, caster->GetGUID()); },
[](Unit const* target) { return !target->IsFullHealth(); },
[](WorldObject const* target) { return target->IsPlayer() || (target->IsCreature() && target->ToCreature()->IsTreatedAsRaidUnit()); },
[caster](Unit const* target) { return target->IsInRaidWith(caster); }
};
}
void Register() override