From 2e3f3fda3fc533daa4064739b633dbb28f5115d3 Mon Sep 17 00:00:00 2001 From: Telegrill Date: Sat, 6 Sep 2025 00:40:47 +0200 Subject: Core/Spells: Add a helper function to sort spell targets based on custom scripted sorting rules --- src/server/game/Spells/Spell.cpp | 63 ++++++++++++++++++++++++++++++++++++++++ src/server/game/Spells/Spell.h | 10 +++++++ 2 files changed, 73 insertions(+) (limited to 'src/server/game') 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& targets, size_t maxTarg targets.resize(maxTargets); std::ranges::transform(tempTargets.begin(), tempTargets.begin() + maxTargets, targets.begin(), Trinity::TupleElement<0>); } + +void SortTargetsWithPriorityRules(std::list& targets, size_t maxTargets, std::vector const& rules) +{ + if (targets.size() <= maxTargets) + return; + + std::vector> 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& left, const std::pair& 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& entry) + { return entry.second == tieScore; }; + + // scan backwards to include tied entries before the cutoff. + std::ptrdiff_t tieStart = static_cast(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(cutOff); + while (tieEnd < static_cast(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& targets, size_t maxTargets, bool prioritizePlayers, Unit const* prioritizeGroupMembersOf = nullptr); + + struct PriorityRules + { + int32 weight; + std::function condition; + }; + + inline std::vector CreatePriorityRules(std::initializer_list rules) { return { rules }; } + + TC_GAME_API void SortTargetsWithPriorityRules(std::list& targets, size_t maxTargets, std::vector const& rules); } extern template void Spell::SearchTargets>(Trinity::WorldObjectListSearcher& searcher, uint32 containerMask, WorldObject* referer, Position const* pos, float radius); -- cgit v1.2.3