Add Sense Undead for Paladins (#2200)

# Pull Request

This PR adds the sense undead ability for Paladins, which they will keep
active at all times. This is mildly useful because the associated minor
glyph provides a 1% damage increase against undead while the ability is
active.

Sense undead is also added to InitClassSpells(). I understand that it is
a trainer spell so would normally be covered by InitAvailableSpells(),
but those playing with mod-individual-progression will not receive the
spell through InitAvailableSpells() because it is removed from trainers
by the mod (in TBC, a quest was required to obtain the spell).

Finally, the minor glyph of sense undead is now added to the config as a
default glyph for all PvE specs. It is not added for PvP specs because
Forsaken do not count as undead so the glyph is useless in PvP. I also
made some other tweaks to Paladin default minor glyphs that are not
worth spending any time talking about.

Edit: I also did some minor reformatting of code and replaced some
numbers with existing constants.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

The implementation just checks if a Paladin has the sense undead aura,
and if not, the Paladin will activate sense undead. It is simple and
cheap.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [ ] No
- - [x] Yes (**describe and justify impact**)

Infinitesimally 

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ ] No
- - [x] Yes (**explain why**)

Paladin bots will by default have sense undead enabled. There is no
disadvantage to this.

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## 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

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
This commit is contained in:
Crow 2026-03-20 14:38:06 -05:00 committed by GitHub
parent 473b2ab5c6
commit 35a0282ca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 71 additions and 80 deletions

View File

@ -1406,28 +1406,28 @@ AiPlayerbot.PremadeSpecLink.1.5.80 = 0502300123-3-250031220223012521332113321
# #
AiPlayerbot.PremadeSpecName.2.0 = holy pve AiPlayerbot.PremadeSpecName.2.0 = holy pve
AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43369,43365,41109 AiPlayerbot.PremadeSpecGlyph.2.0 = 41106,43367,45741,43368,43365,41109
AiPlayerbot.PremadeSpecLink.2.0.60 = 50350151020013053100515221 AiPlayerbot.PremadeSpecLink.2.0.60 = 50350151020013053100515221
AiPlayerbot.PremadeSpecLink.2.0.80 = 50350152220013053100515221-503201312 AiPlayerbot.PremadeSpecLink.2.0.80 = 50350152220013053100515221-503201312
AiPlayerbot.PremadeSpecName.2.1 = prot pve AiPlayerbot.PremadeSpecName.2.1 = prot pve
AiPlayerbot.PremadeSpecGlyph.2.1 = 41099,43367,43869,43369,43365,45745 AiPlayerbot.PremadeSpecGlyph.2.1 = 41099,43367,43869,43368,43369,45745
AiPlayerbot.PremadeSpecLink.2.1.60 = -05005135203102311333112321 AiPlayerbot.PremadeSpecLink.2.1.60 = -05005135203102311333112321
AiPlayerbot.PremadeSpecLink.2.1.80 = -05005135203102311333312321-502302012003 AiPlayerbot.PremadeSpecLink.2.1.80 = -05005135203102311333312321-502302012003
AiPlayerbot.PremadeSpecName.2.2 = ret pve AiPlayerbot.PremadeSpecName.2.2 = ret pve
AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43369,43365,43869 AiPlayerbot.PremadeSpecGlyph.2.2 = 41092,43367,41099,43368,43369,43869
AiPlayerbot.PremadeSpecLink.2.2.60 = --05230051203331302133231131 AiPlayerbot.PremadeSpecLink.2.2.60 = --05230051203331302133231131
AiPlayerbot.PremadeSpecLink.2.2.65 = -05-05230051203331302133231131 AiPlayerbot.PremadeSpecLink.2.2.65 = -05-05230051203331302133231131
AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331 AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
AiPlayerbot.PremadeSpecName.2.3 = holy pvp AiPlayerbot.PremadeSpecName.2.3 = holy pvp
AiPlayerbot.PremadeSpecGlyph.2.3 = 41110,43367,45746,43366,43365,45747 AiPlayerbot.PremadeSpecGlyph.2.3 = 41110,43367,45746,43369,43365,45747
AiPlayerbot.PremadeSpecLink.2.3.60 = 50332150300013050133215221 AiPlayerbot.PremadeSpecLink.2.3.60 = 50332150300013050133215221
AiPlayerbot.PremadeSpecLink.2.3.80 = 50332150300013050133315221-5032013122 AiPlayerbot.PremadeSpecLink.2.3.80 = 50332150300013050133315221-5032013122
AiPlayerbot.PremadeSpecName.2.4 = prot pvp AiPlayerbot.PremadeSpecName.2.4 = prot pvp
AiPlayerbot.PremadeSpecGlyph.2.4 = 41092,43369,41101,43368,43365,45745 AiPlayerbot.PremadeSpecGlyph.2.4 = 41092,43367,41101,43369,43365,45745
AiPlayerbot.PremadeSpecLink.2.4.60 = -15320130223122311323311321 AiPlayerbot.PremadeSpecLink.2.4.60 = -15320130223122311323311321
AiPlayerbot.PremadeSpecLink.2.4.80 = -15320130223122321333312321-052300502 AiPlayerbot.PremadeSpecLink.2.4.80 = -15320130223122321333312321-052300502
AiPlayerbot.PremadeSpecName.2.5 = ret pvp AiPlayerbot.PremadeSpecName.2.5 = ret pvp
AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43369,41102,43368,43365,45747 AiPlayerbot.PremadeSpecGlyph.2.5 = 41095,43367,41102,43369,43365,45747
AiPlayerbot.PremadeSpecLink.2.5.60 = --05230250203331222133201321 AiPlayerbot.PremadeSpecLink.2.5.60 = --05230250203331222133201321
AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321 AiPlayerbot.PremadeSpecLink.2.5.80 = -1532013022-05230250203331322133201321

View File

@ -472,9 +472,8 @@ Unit* CastRighteousDefenseAction::GetTarget()
{ {
Unit* current_target = AI_VALUE(Unit*, "current target"); Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target) if (!current_target)
{ return nullptr;
return NULL;
}
return current_target->GetVictim(); return current_target->GetVictim();
} }

View File

@ -91,9 +91,8 @@ public:
class CastBlessingOnPartyAction : public BuffOnPartyAction class CastBlessingOnPartyAction : public BuffOnPartyAction
{ {
public: public:
CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name) : BuffOnPartyAction(botAI, name), name(name) CastBlessingOnPartyAction(PlayerbotAI* botAI, std::string const name)
{ : BuffOnPartyAction(botAI, name), name(name) {}
}
Value<Unit*>* GetTargetValue() override; Value<Unit*>* GetTargetValue() override;
@ -154,9 +153,7 @@ public:
class CastBlessingOfSanctuaryOnPartyAction : public BuffOnPartyAction class CastBlessingOfSanctuaryOnPartyAction : public BuffOnPartyAction
{ {
public: public:
CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary") CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "blessing of sanctuary") {}
{
}
std::string const getName() override { return "blessing of sanctuary on party"; } std::string const getName() override { return "blessing of sanctuary on party"; }
Value<Unit*>* GetTargetValue() override; Value<Unit*>* GetTargetValue() override;
@ -173,18 +170,14 @@ class CastHolyShockOnPartyAction : public HealPartyMemberAction
{ {
public: public:
CastHolyShockOnPartyAction(PlayerbotAI* botAI) CastHolyShockOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "holy shock", 25.0f, HealingManaEfficiency::LOW) : HealPartyMemberAction(botAI, "holy shock", 25.0f, HealingManaEfficiency::LOW) {}
{
}
}; };
class CastHolyLightOnPartyAction : public HealPartyMemberAction class CastHolyLightOnPartyAction : public HealPartyMemberAction
{ {
public: public:
CastHolyLightOnPartyAction(PlayerbotAI* botAI) CastHolyLightOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "holy light", 50.0f, HealingManaEfficiency::MEDIUM) : HealPartyMemberAction(botAI, "holy light", 50.0f, HealingManaEfficiency::MEDIUM) {}
{
}
}; };
class CastFlashOfLightAction : public CastHealingSpellAction class CastFlashOfLightAction : public CastHealingSpellAction
@ -197,9 +190,7 @@ class CastFlashOfLightOnPartyAction : public HealPartyMemberAction
{ {
public: public:
CastFlashOfLightOnPartyAction(PlayerbotAI* botAI) CastFlashOfLightOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "flash of light", 15.0f, HealingManaEfficiency::HIGH) : HealPartyMemberAction(botAI, "flash of light", 15.0f, HealingManaEfficiency::HIGH) {}
{
}
}; };
class CastLayOnHandsAction : public CastHealingSpellAction class CastLayOnHandsAction : public CastHealingSpellAction
@ -357,9 +348,7 @@ class CastHammerOfJusticeOnEnemyHealerAction : public CastSpellOnEnemyHealerActi
{ {
public: public:
CastHammerOfJusticeOnEnemyHealerAction(PlayerbotAI* botAI) CastHammerOfJusticeOnEnemyHealerAction(PlayerbotAI* botAI)
: CastSpellOnEnemyHealerAction(botAI, "hammer of justice") : CastSpellOnEnemyHealerAction(botAI, "hammer of justice") {}
{
}
}; };
class CastHammerOfJusticeSnareAction : public CastSnareSpellAction class CastHammerOfJusticeSnareAction : public CastSnareSpellAction
@ -368,6 +357,12 @@ public:
CastHammerOfJusticeSnareAction(PlayerbotAI* botAI) : CastSnareSpellAction(botAI, "hammer of justice") {} CastHammerOfJusticeSnareAction(PlayerbotAI* botAI) : CastSnareSpellAction(botAI, "hammer of justice") {}
}; };
class CastSenseUndeadAction : public CastBuffSpellAction
{
public:
CastSenseUndeadAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "sense undead") {}
};
class CastTurnUndeadAction : public CastBuffSpellAction class CastTurnUndeadAction : public CastBuffSpellAction
{ {
public: public:
@ -381,25 +376,25 @@ PROTECT_ACTION(CastBlessingOfProtectionProtectAction, "blessing of protection");
class CastDivinePleaAction : public CastBuffSpellAction class CastDivinePleaAction : public CastBuffSpellAction
{ {
public: public:
CastDivinePleaAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine plea") {} CastDivinePleaAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "divine plea") {}
}; };
class ShieldOfRighteousnessAction : public CastMeleeSpellAction class ShieldOfRighteousnessAction : public CastMeleeSpellAction
{ {
public: public:
ShieldOfRighteousnessAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "shield of righteousness") {} ShieldOfRighteousnessAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shield of righteousness") {}
}; };
class CastBeaconOfLightOnMainTankAction : public BuffOnMainTankAction class CastBeaconOfLightOnMainTankAction : public BuffOnMainTankAction
{ {
public: public:
CastBeaconOfLightOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "beacon of light", true) {} CastBeaconOfLightOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "beacon of light", true) {}
}; };
class CastSacredShieldOnMainTankAction : public BuffOnMainTankAction class CastSacredShieldOnMainTankAction : public BuffOnMainTankAction
{ {
public: public:
CastSacredShieldOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "sacred shield", false) {} CastSacredShieldOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "sacred shield", false) {}
}; };
class CastAvengingWrathAction : public CastBuffSpellAction class CastAvengingWrathAction : public CastBuffSpellAction
@ -428,4 +423,5 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override; bool isUseful() override;
}; };
#endif #endif

View File

@ -132,6 +132,7 @@ public:
&PaladinTriggerFactoryInternal::hammer_of_justice_on_enemy_target; &PaladinTriggerFactoryInternal::hammer_of_justice_on_enemy_target;
creators["hammer of justice on snare target"] = creators["hammer of justice on snare target"] =
&PaladinTriggerFactoryInternal::hammer_of_justice_on_snare_target; &PaladinTriggerFactoryInternal::hammer_of_justice_on_snare_target;
creators["not sensing undead"] = &PaladinTriggerFactoryInternal::not_sensing_undead;
creators["divine favor"] = &PaladinTriggerFactoryInternal::divine_favor; creators["divine favor"] = &PaladinTriggerFactoryInternal::divine_favor;
creators["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead; creators["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead;
creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield; creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield;
@ -151,6 +152,7 @@ public:
} }
private: private:
static Trigger* not_sensing_undead(PlayerbotAI* botAI) { return new NotSensingUndeadTrigger(botAI); }
static Trigger* turn_undead(PlayerbotAI* botAI) { return new TurnUndeadTrigger(botAI); } static Trigger* turn_undead(PlayerbotAI* botAI) { return new TurnUndeadTrigger(botAI); }
static Trigger* divine_favor(PlayerbotAI* botAI) { return new DivineFavorTrigger(botAI); } static Trigger* divine_favor(PlayerbotAI* botAI) { return new DivineFavorTrigger(botAI); }
static Trigger* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); } static Trigger* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); }
@ -288,6 +290,7 @@ public:
creators["hammer of justice on snare target"] = creators["hammer of justice on snare target"] =
&PaladinAiObjectContextInternal::hammer_of_justice_on_snare_target; &PaladinAiObjectContextInternal::hammer_of_justice_on_snare_target;
creators["divine favor"] = &PaladinAiObjectContextInternal::divine_favor; creators["divine favor"] = &PaladinAiObjectContextInternal::divine_favor;
creators["sense undead"] = &PaladinAiObjectContextInternal::sense_undead;
creators["turn undead"] = &PaladinAiObjectContextInternal::turn_undead; creators["turn undead"] = &PaladinAiObjectContextInternal::turn_undead;
creators["blessing of protection on party"] = &PaladinAiObjectContextInternal::blessing_of_protection_on_party; creators["blessing of protection on party"] = &PaladinAiObjectContextInternal::blessing_of_protection_on_party;
creators["righteous defense"] = &PaladinAiObjectContextInternal::righteous_defense; creators["righteous defense"] = &PaladinAiObjectContextInternal::righteous_defense;
@ -312,6 +315,7 @@ private:
{ {
return new CastBlessingOfProtectionProtectAction(botAI); return new CastBlessingOfProtectionProtectAction(botAI);
} }
static Action* sense_undead(PlayerbotAI* botAI) { return new CastSenseUndeadAction(botAI); }
static Action* turn_undead(PlayerbotAI* botAI) { return new CastTurnUndeadAction(botAI); } static Action* turn_undead(PlayerbotAI* botAI) { return new CastTurnUndeadAction(botAI); }
static Action* divine_favor(PlayerbotAI* botAI) { return new CastDivineFavorAction(botAI); } static Action* divine_favor(PlayerbotAI* botAI) { return new CastDivineFavorAction(botAI); }
static Action* righteous_fury(PlayerbotAI* botAI) { return new CastRighteousFuryAction(botAI); } static Action* righteous_fury(PlayerbotAI* botAI) { return new CastRighteousFuryAction(botAI); }

View File

@ -19,14 +19,15 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
NonCombatStrategy::InitTriggers(triggers); NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("party member dead", { NextAction("redemption", ACTION_CRITICAL_HEAL + 10) })); triggers.push_back(new TriggerNode("party member dead", { NextAction("redemption", ACTION_CRITICAL_HEAL + 10) }));
triggers.push_back(new TriggerNode("party member almost full health", { NextAction("flash of light on party", 25.0f) })); triggers.push_back(new TriggerNode("party member almost full health", { NextAction("flash of light on party", ACTION_MEDIUM_HEAL + 5.0f) }));
triggers.push_back(new TriggerNode("party member medium health", { NextAction("flash of light on party", 26.0f) })); triggers.push_back(new TriggerNode("party member medium health", { NextAction("flash of light on party", ACTION_MEDIUM_HEAL + 6.0f) }));
triggers.push_back(new TriggerNode("party member low health", { NextAction("holy light on party", 27.0f) })); triggers.push_back(new TriggerNode("party member low health", { NextAction("holy light on party", ACTION_MEDIUM_HEAL + 7.0f) }));
triggers.push_back(new TriggerNode("party member critical health", { NextAction("holy light on party", 28.0f) })); triggers.push_back(new TriggerNode("party member critical health", { NextAction("holy light on party", ACTION_MEDIUM_HEAL + 8.0f) }));
triggers.push_back(new TriggerNode("not sensing undead", { NextAction("sense undead", ACTION_IDLE + 1.0f) }));
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot()); int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
if (specTab == 0 || specTab == 1) // Holy or Protection if (specTab == PALADIN_TAB_HOLY || specTab == PALADIN_TAB_PROTECTION)
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) })); triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
if (specTab == 2) // Retribution if (specTab == PALADIN_TAB_RETRIBUTION)
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) })); triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) }));
} }

View File

@ -30,3 +30,8 @@ bool BlessingTrigger::IsActive()
return SpellTrigger::IsActive() && !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom", return SpellTrigger::IsActive() && !botAI->HasAnyAuraOf(target, "blessing of might", "blessing of wisdom",
"blessing of kings", "blessing of sanctuary", nullptr); "blessing of kings", "blessing of sanctuary", nullptr);
} }
bool NotSensingUndeadTrigger::IsActive()
{
return !botAI->HasAura("sense undead", bot);
}

View File

@ -77,9 +77,7 @@ class BlessingOnPartyTrigger : public BuffOnPartyTrigger
{ {
public: public:
BlessingOnPartyTrigger(PlayerbotAI* botAI) BlessingOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of kings,blessing of might,blessing of wisdom", 2 * 2000) : BuffOnPartyTrigger(botAI, "blessing of kings,blessing of might,blessing of wisdom", 2 * 2000) {}
{
}
}; };
class BlessingTrigger : public BuffTrigger class BlessingTrigger : public BuffTrigger
@ -93,7 +91,8 @@ public:
class HammerOfJusticeInterruptSpellTrigger : public InterruptSpellTrigger class HammerOfJusticeInterruptSpellTrigger : public InterruptSpellTrigger
{ {
public: public:
HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "hammer of justice") {} HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI)
: InterruptSpellTrigger(botAI, "hammer of justice") {}
}; };
class HammerOfJusticeSnareTrigger : public SnareTargetTrigger class HammerOfJusticeSnareTrigger : public SnareTargetTrigger
@ -144,9 +143,7 @@ class CleanseCurePartyMemberDiseaseTrigger : public PartyMemberNeedCureTrigger
{ {
public: public:
CleanseCurePartyMemberDiseaseTrigger(PlayerbotAI* botAI) CleanseCurePartyMemberDiseaseTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE) : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE) {}
{
}
}; };
class CleanseCurePoisonTrigger : public NeedCureTrigger class CleanseCurePoisonTrigger : public NeedCureTrigger
@ -159,9 +156,7 @@ class CleanseCurePartyMemberPoisonTrigger : public PartyMemberNeedCureTrigger
{ {
public: public:
CleanseCurePartyMemberPoisonTrigger(PlayerbotAI* botAI) CleanseCurePartyMemberPoisonTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON) : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON) {}
{
}
}; };
class CleanseCureMagicTrigger : public NeedCureTrigger class CleanseCureMagicTrigger : public NeedCureTrigger
@ -173,15 +168,15 @@ public:
class CleanseCurePartyMemberMagicTrigger : public PartyMemberNeedCureTrigger class CleanseCurePartyMemberMagicTrigger : public PartyMemberNeedCureTrigger
{ {
public: public:
CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC) CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI)
{ : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC) {}
}
}; };
class HammerOfJusticeEnemyHealerTrigger : public InterruptEnemyHealerTrigger class HammerOfJusticeEnemyHealerTrigger : public InterruptEnemyHealerTrigger
{ {
public: public:
HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "hammer of justice") {} HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI)
: InterruptEnemyHealerTrigger(botAI, "hammer of justice") {}
}; };
class DivineFavorTrigger : public BuffTrigger class DivineFavorTrigger : public BuffTrigger
@ -190,6 +185,14 @@ public:
DivineFavorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine favor") {} DivineFavorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine favor") {}
}; };
class NotSensingUndeadTrigger : public BuffTrigger
{
public:
NotSensingUndeadTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "not sensing undead") {}
bool IsActive() override;
};
class TurnUndeadTrigger : public HasCcTargetTrigger class TurnUndeadTrigger : public HasCcTargetTrigger
{ {
public: public:
@ -201,7 +204,8 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield");
class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger
{ {
public: public:
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "beacon of light", true) {} BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai)
: BuffOnMainTankTrigger(ai, "beacon of light", true) {}
}; };
class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger
@ -213,34 +217,29 @@ public:
class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger
{ {
public: public:
BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {} BlessingOfKingsOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of kings", 2 * 2000) {}
}; };
class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger class BlessingOfWisdomOnPartyTrigger : public BuffOnPartyTrigger
{ {
public: public:
BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI) BlessingOfWisdomOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) : BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
{
}
}; };
class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger class BlessingOfMightOnPartyTrigger : public BuffOnPartyTrigger
{ {
public: public:
BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI) BlessingOfMightOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) : BuffOnPartyTrigger(botAI, "blessing of might,blessing of wisdom", 2 * 2000) {}
{
}
}; };
class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger class BlessingOfSanctuaryOnPartyTrigger : public BuffOnPartyTrigger
{ {
public: public:
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI) BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) : BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
{
}
}; };
class AvengingWrathTrigger : public BoostTrigger class AvengingWrathTrigger : public BoostTrigger
@ -248,4 +247,5 @@ class AvengingWrathTrigger : public BoostTrigger
public: public:
AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {} AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {}
}; };
#endif #endif

View File

@ -2553,17 +2553,15 @@ void PlayerbotFactory::InitClassSpells()
bot->learnSpell(7386, false); // Sunder Armor bot->learnSpell(7386, false); // Sunder Armor
} }
if (level >= 30) if (level >= 30)
{
bot->learnSpell(2458, false); // Berserker Stance bot->learnSpell(2458, false); // Berserker Stance
}
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
bot->learnSpell(21084, true); bot->learnSpell(21084, true);
bot->learnSpell(635, true); bot->learnSpell(635, true);
if (level >= 12) if (level >= 12)
{
bot->learnSpell(7328, false); // Redemption bot->learnSpell(7328, false); // Redemption
} if (level >= 20)
bot->learnSpell(5502, false); // Sense Undead
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
bot->learnSpell(1752, true); bot->learnSpell(1752, true);
@ -2605,17 +2603,11 @@ void PlayerbotFactory::InitClassSpells()
bot->learnSpell(686, true); bot->learnSpell(686, true);
bot->learnSpell(688, false); // summon imp bot->learnSpell(688, false); // summon imp
if (level >= 10) if (level >= 10)
{
bot->learnSpell(697, false); // summon voidwalker bot->learnSpell(697, false); // summon voidwalker
}
if (level >= 20) if (level >= 20)
{
bot->learnSpell(712, false); // summon succubus bot->learnSpell(712, false); // summon succubus
}
if (level >= 30) if (level >= 30)
{
bot->learnSpell(691, false); // summon felhunter bot->learnSpell(691, false); // summon felhunter
}
break; break;
case CLASS_DRUID: case CLASS_DRUID:
bot->learnSpell(5176, true); bot->learnSpell(5176, true);
@ -2632,17 +2624,11 @@ void PlayerbotFactory::InitClassSpells()
bot->learnSpell(331, true); bot->learnSpell(331, true);
// bot->learnSpell(66747, true); // Totem of the Earthen Ring // bot->learnSpell(66747, true); // Totem of the Earthen Ring
if (level >= 4) if (level >= 4)
{
bot->learnSpell(8071, false); // stoneskin totem bot->learnSpell(8071, false); // stoneskin totem
}
if (level >= 10) if (level >= 10)
{
bot->learnSpell(3599, false); // searing totem bot->learnSpell(3599, false); // searing totem
}
if (level >= 20) if (level >= 20)
{
bot->learnSpell(5394, false); // healing stream totem bot->learnSpell(5394, false); // healing stream totem
}
break; break;
default: default:
break; break;