aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTelegrill <nyrdeveloper@gmail.com>2025-09-06 00:40:47 +0200
committerShauren <shauren.trinity@gmail.com>2025-09-06 13:19:38 +0200
commit2e3f3fda3fc533daa4064739b633dbb28f5115d3 (patch)
tree6775145b5e44b6f34bf5afb45db4e41ca380b071 /src
parent52cb4cf9f166c1cf7cd02a0b1e2b25f18ab0024a (diff)
Core/Spells: Add a helper function to sort spell targets based on custom scripted sorting rules
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Spells/Spell.cpp63
-rw-r--r--src/server/game/Spells/Spell.h10
-rw-r--r--src/server/scripts/Spells/spell_druid.cpp3
-rw-r--r--src/server/scripts/Spells/spell_priest.cpp51
-rw-r--r--src/server/scripts/Spells/spell_shaman.cpp5
5 files changed, 89 insertions, 43 deletions
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 58996ebb379..bce4b676490 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -9632,6 +9632,69 @@ void SelectRandomInjuredTargets(std::list<WorldObject*>& targets, size_t maxTarg
targets.resize(maxTargets);
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)
+{
+ if (targets.size() <= maxTargets)
+ return;
+
+ std::vector<std::pair<WorldObject*, int32>> prioritizedTargets;
+
+ // score each target based on how many rules they satisfy.
+ for (WorldObject* object : targets)
+ {
+ Unit* unit = object ? object->ToUnit() : nullptr;
+ if (!unit)
+ continue;
+
+ int32 score = 0;
+
+ 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;
+ });
+
+ size_t cutOff = std::min(maxTargets, prioritizedTargets.size());
+
+ // if there are ties at the cutoff, shuffle them to avoid selection bias.
+ if (cutOff < prioritizedTargets.size())
+ {
+ 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;
+
+ // shuffle only the tied range to randomize final selection.
+ Containers::RandomShuffle(
+ std::ranges::next(prioritizedTargets.begin(), tieStart),
+ std::ranges::next(prioritizedTargets.begin(), tieEnd));
+ }
+
+ targets.clear();
+
+ for (size_t i = 0; i < cutOff; ++i)
+ targets.push_back(prioritizedTargets[i].first);
+}
} //namespace Trinity
CastSpellTargetArg::CastSpellTargetArg(WorldObject* target)
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index 2fb3d3ae4c9..d6f968420d8 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -1054,6 +1054,16 @@ namespace Trinity
};
TC_GAME_API void SelectRandomInjuredTargets(std::list<WorldObject*>& targets, size_t maxTargets, bool prioritizePlayers, Unit const* prioritizeGroupMembersOf = nullptr);
+
+ struct PriorityRules
+ {
+ int32 weight;
+ std::function<bool(Unit*)> condition;
+ };
+
+ 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::vector<PriorityRules> const& rules);
}
extern template void Spell::SearchTargets<Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck>>(Trinity::WorldObjectListSearcher<Trinity::WorldObjectSpellAreaTargetCheck>& searcher, uint32 containerMask, WorldObject* referer, Position const* pos, float radius);
diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp
index 958dfe1ed68..2773f68a067 100644
--- a/src/server/scripts/Spells/spell_druid.cpp
+++ b/src/server/scripts/Spells/spell_druid.cpp
@@ -753,7 +753,6 @@ class spell_dru_efflorescence_heal : public SpellScript
{
void FilterTargets(std::list<WorldObject*>& targets) const
{
- // Efflorescence became a smart heal which prioritizes players and their pets in their group before any unit outside their group.
Trinity::SelectRandomInjuredTargets(targets, 3, true, GetCaster());
}
@@ -2542,7 +2541,7 @@ class spell_dru_yseras_gift : public AuraScript
// 145110 - Ysera's Gift (heal)
class spell_dru_yseras_gift_group_heal : public SpellScript
{
- void SelectTargets(std::list<WorldObject*>& targets)
+ static void SelectTargets(SpellScript const&, std::list<WorldObject*>& targets)
{
Trinity::SelectRandomInjuredTargets(targets, 1, true);
}
diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp
index a094b243e1d..ddd3928ab0f 100644
--- a/src/server/scripts/Spells/spell_priest.cpp
+++ b/src/server/scripts/Spells/spell_priest.cpp
@@ -2115,28 +2115,15 @@ class spell_pri_power_word_radiance : public SpellScript
void FilterTargets(std::list<WorldObject*>& targets)
{
+ Unit* caster = GetCaster();
Unit* explTarget = GetExplTargetUnit();
// we must add one since explicit target is always chosen.
uint32 maxTargets = GetEffectInfo(EFFECT_2).CalcValue(GetCaster()) + 1;
- if (targets.size() > maxTargets)
- {
- // priority is: a) no Atonement b) injured c) anything else (excluding explicit target which is always added).
- targets.sort([this, explTarget](WorldObject* lhs, WorldObject* rhs)
- {
- if (lhs == explTarget) // explTarget > anything: always true
- return true;
- if (rhs == explTarget) // anything > explTarget: always false
- return false;
-
- return MakeSortTuple(lhs) > MakeSortTuple(rhs);
- });
+ SortTargetsWithPriorityRules(targets, maxTargets, GetRadianceRules(caster, explTarget));
- targets.resize(maxTargets);
- }
-
- for (WorldObject* target : targets)
+ for (WorldObject const* target : targets)
{
if (target == explTarget)
continue;
@@ -2152,30 +2139,22 @@ class spell_pri_power_word_radiance : public SpellScript
GetHitUnit()->SendPlaySpellVisual(target, SPELL_VISUAL_PRIEST_POWER_WORD_RADIANCE, 0, 0, 70.0f);
}
- void Register() override
- {
- OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_pri_power_word_radiance::FilterTargets, EFFECT_1, TARGET_UNIT_DEST_AREA_ALLY);
- OnEffectHitTarget += SpellEffectFn(spell_pri_power_word_radiance::HandleEffectHitTarget, EFFECT_0, SPELL_EFFECT_DUMMY);
- }
-
-private:
- std::tuple<bool, bool> MakeSortTuple(WorldObject* obj) const
- {
- return std::make_tuple(IsUnitWithNoAtonement(obj), IsUnitInjured(obj));
- }
-
- // Returns true if obj is a unit but has no atonement
- bool IsUnitWithNoAtonement(WorldObject* obj) const
+ static std::vector<Trinity::PriorityRules> GetRadianceRules(Unit const* caster, Unit const* explTarget)
{
- Unit* unit = obj->ToUnit();
- return unit && !unit->HasAura(SPELL_PRIEST_ATONEMENT_EFFECT, GetCaster()->GetGUID());
+ 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; }}
+ });
}
- // Returns true if obj is a unit and is injured
- static bool IsUnitInjured(WorldObject* obj)
+ void Register() override
{
- Unit* unit = obj->ToUnit();
- return unit && !unit->IsFullHealth();
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_pri_power_word_radiance::FilterTargets, EFFECT_1, TARGET_UNIT_DEST_AREA_ALLY);
+ OnEffectHitTarget += SpellEffectFn(spell_pri_power_word_radiance::HandleEffectHitTarget, EFFECT_0, SPELL_EFFECT_DUMMY);
}
std::vector<ObjectGuid> _visualTargets;
diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp
index b8dbbcd81f5..1f7f1842478 100644
--- a/src/server/scripts/Spells/spell_shaman.cpp
+++ b/src/server/scripts/Spells/spell_shaman.cpp
@@ -297,11 +297,6 @@ class spell_sha_ancestral_guidance : public AuraScript
// 114911 - Ancestral Guidance Heal
class spell_sha_ancestral_guidance_heal : public SpellScript
{
- bool Validate(SpellInfo const* /*spellInfo*/) override
- {
- return ValidateSpellInfo({ SPELL_SHAMAN_ANCESTRAL_GUIDANCE });
- }
-
static void ResizeTargets(SpellScript const&, std::list<WorldObject*>& targets)
{
Trinity::SelectRandomInjuredTargets(targets, 3, true);