From d07ddb14d091111ae01157149c8f2997fc52a304 Mon Sep 17 00:00:00 2001 From: kadeshar Date: Sat, 4 Apr 2026 07:31:17 +0200 Subject: [PATCH] Paladin use bubble heal strategy (#2244) ## Pull Request Description Added support for bubble heal strategy. Adjusted emergency action order ## How to Test the Changes - invite paladin bot to party - check that bot dont have `healer dps` strategy - start combat (with for example dummy) - use `.damage 20000` where 20000 is near max health of bot - use `.unaura 25771` until bot use Divine Shield - bot should heal himself ## Impact Assessment - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - - [x] No, not at all - - [ ] Minimal impact (**explain below**) - - [ ] Moderate impact (**explain below**) - Does this change modify default bot behavior? - - [ ] No - - [x] Yes (**explain why**) Paladin bot heal himself without `healer dps` while Divine Shield - Does this change add new decision branches or increase maintenance complexity? - - [x] No - - [ ] Yes (**explain below**) ## Messages to Translate - Does this change add bot messages to translate? - - [x] No - - [ ] Yes (**list messages in the table**) | Message key | Default message | | --------------- | ------------------ | | | | | | | ## AI Assistance - Was AI assistance used while working on this change? - - [x] No - - [ ] Yes (**explain below**) ## Final Checklist - - [x] Stability is not compromised. - - [x] Performance impact is understood, tested, and acceptable. - - [x] Added logic complexity is justified and explained. - - [x] Documentation updated if needed (Conf comments, WiKi commands). ## Notes for Reviewers obraz --- .../Class/Paladin/PaladinAiObjectContext.cpp | 2 ++ .../Strategy/GenericPaladinStrategy.cpp | 22 +++++++++++-------- .../Class/Paladin/Trigger/PaladinTriggers.cpp | 5 +++++ .../Class/Paladin/Trigger/PaladinTriggers.h | 8 +++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp index 3c92e57c3..7edbf5c8f 100644 --- a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp +++ b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp @@ -134,6 +134,7 @@ public: &PaladinTriggerFactoryInternal::hammer_of_justice_on_snare_target; creators["not sensing undead"] = &PaladinTriggerFactoryInternal::not_sensing_undead; creators["divine favor"] = &PaladinTriggerFactoryInternal::divine_favor; + creators["divine shield low health"] = &PaladinTriggerFactoryInternal::divine_shield_low_health; creators["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead; creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield; creators["consecration"] = &PaladinTriggerFactoryInternal::consecration; @@ -156,6 +157,7 @@ private: static Trigger* not_sensing_undead(PlayerbotAI* botAI) { return new NotSensingUndeadTrigger(botAI); } static Trigger* turn_undead(PlayerbotAI* botAI) { return new TurnUndeadTrigger(botAI); } static Trigger* divine_favor(PlayerbotAI* botAI) { return new DivineFavorTrigger(botAI); } + static Trigger* divine_shield_low_health(PlayerbotAI* botAI) { return new DivineShieldLowHealthTrigger(botAI); } static Trigger* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); } static Trigger* righteous_fury(PlayerbotAI* botAI) { return new RighteousFuryTrigger(botAI); } static Trigger* judgement(PlayerbotAI* botAI) { return new JudgementTrigger(botAI); } diff --git a/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp b/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp index f03207216..9a197c601 100644 --- a/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp @@ -16,17 +16,21 @@ void GenericPaladinStrategy::InitTriggers(std::vector& triggers) { CombatStrategy::InitTriggers(triggers); - triggers.push_back(new TriggerNode("critical health", { NextAction("divine shield", - ACTION_HIGH + 5) })); - triggers.push_back( - new TriggerNode("hammer of justice interrupt", - { NextAction("hammer of justice", ACTION_INTERRUPT) })); - triggers.push_back(new TriggerNode( - "hammer of justice on enemy healer", + triggers.push_back(new TriggerNode("hammer of justice interrupt", + { NextAction("hammer of justice", ACTION_INTERRUPT) })); + triggers.push_back(new TriggerNode("hammer of justice on enemy healer", { NextAction("hammer of justice on enemy healer", ACTION_INTERRUPT) })); - triggers.push_back(new TriggerNode( - "hammer of justice on snare target", + triggers.push_back(new TriggerNode("hammer of justice on snare target", { NextAction("hammer of justice on snare target", ACTION_INTERRUPT) })); + triggers.push_back(new TriggerNode("critical health", { NextAction("divine shield", ACTION_EMERGENCY) })); + triggers.push_back(new TriggerNode("critical health", { NextAction("lay on hands", ACTION_EMERGENCY + 1) })); + triggers.push_back(new TriggerNode("party member critical health", + { NextAction("lay on hands on party", ACTION_EMERGENCY + 2) })); + triggers.push_back(new TriggerNode("divine shield low health", + { NextAction("flash of light", ACTION_EMERGENCY + 3), NextAction("holy light", ACTION_EMERGENCY + 2)})); + triggers.push_back(new TriggerNode("protect party member", + { NextAction("blessing of protection on party", ACTION_EMERGENCY + 3) })); + triggers.push_back(new TriggerNode("high mana", { NextAction("divine plea", ACTION_HIGH) })); triggers.push_back(new TriggerNode( "critical health", { NextAction("lay on hands", ACTION_EMERGENCY) })); triggers.push_back( diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp index e3367aaef..46a2d8a94 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp @@ -32,6 +32,11 @@ bool BlessingTrigger::IsActive() "blessing of kings", "blessing of sanctuary", nullptr); } +bool DivineShieldLowHealthTrigger::IsActive() +{ + return botAI->HasAura("divine shield", bot) && AI_VALUE2(uint8, "health", "self target") < 80; +} + Unit* HandOfFreedomOnPartyTrigger::GetTarget() { bool const selfImpaired = botAI->IsMovementImpaired(bot); diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h index cc6ceddcf..d11c8024f 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h @@ -185,6 +185,14 @@ public: DivineFavorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine favor") {} }; +class DivineShieldLowHealthTrigger : public Trigger +{ +public: + DivineShieldLowHealthTrigger(PlayerbotAI* botAI) : Trigger(botAI, "divine shield low health") {} + + bool IsActive() override; +}; + class NotSensingUndeadTrigger : public BuffTrigger { public: