diff options
-rw-r--r-- | src/server/game/Spells/Spell.cpp | 63 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.h | 10 | ||||
-rw-r--r-- | src/server/scripts/Spells/spell_druid.cpp | 3 | ||||
-rw-r--r-- | src/server/scripts/Spells/spell_priest.cpp | 51 | ||||
-rw-r--r-- | src/server/scripts/Spells/spell_shaman.cpp | 5 |
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); |