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.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.80 = 50350152220013053100515221-503201312
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.80 = -05005135203102311333312321-502302012003
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.65 = -05-05230051203331302133231131
AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
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.80 = 50332150300013050133315221-5032013122
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.80 = -15320130223122321333312321-052300502
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.80 = -1532013022-05230250203331322133201321

View File

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

View File

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

View File

@ -132,6 +132,7 @@ public:
&PaladinTriggerFactoryInternal::hammer_of_justice_on_enemy_target;
creators["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["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead;
creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield;
@ -151,6 +152,7 @@ public:
}
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* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); }
@ -288,6 +290,7 @@ public:
creators["hammer of justice on snare target"] =
&PaladinAiObjectContextInternal::hammer_of_justice_on_snare_target;
creators["divine favor"] = &PaladinAiObjectContextInternal::divine_favor;
creators["sense undead"] = &PaladinAiObjectContextInternal::sense_undead;
creators["turn undead"] = &PaladinAiObjectContextInternal::turn_undead;
creators["blessing of protection on party"] = &PaladinAiObjectContextInternal::blessing_of_protection_on_party;
creators["righteous defense"] = &PaladinAiObjectContextInternal::righteous_defense;
@ -312,6 +315,7 @@ private:
{
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* divine_favor(PlayerbotAI* botAI) { return new CastDivineFavorAction(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);
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 medium health", { NextAction("flash of light on party", 26.0f) }));
triggers.push_back(new TriggerNode("party member low health", { NextAction("holy light on party", 27.0f) }));
triggers.push_back(new TriggerNode("party member critical health", { NextAction("holy light on party", 28.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", ACTION_MEDIUM_HEAL + 6.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", 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());
if (specTab == 0 || specTab == 1) // Holy or Protection
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
if (specTab == 2) // Retribution
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
if (specTab == PALADIN_TAB_HOLY || specTab == PALADIN_TAB_PROTECTION)
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
if (specTab == PALADIN_TAB_RETRIBUTION)
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",
"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:
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
@ -93,7 +91,8 @@ public:
class HammerOfJusticeInterruptSpellTrigger : public InterruptSpellTrigger
{
public:
HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "hammer of justice") {}
HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* botAI)
: InterruptSpellTrigger(botAI, "hammer of justice") {}
};
class HammerOfJusticeSnareTrigger : public SnareTargetTrigger
@ -144,9 +143,7 @@ class CleanseCurePartyMemberDiseaseTrigger : public PartyMemberNeedCureTrigger
{
public:
CleanseCurePartyMemberDiseaseTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE)
{
}
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_DISEASE) {}
};
class CleanseCurePoisonTrigger : public NeedCureTrigger
@ -159,9 +156,7 @@ class CleanseCurePartyMemberPoisonTrigger : public PartyMemberNeedCureTrigger
{
public:
CleanseCurePartyMemberPoisonTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON)
{
}
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_POISON) {}
};
class CleanseCureMagicTrigger : public NeedCureTrigger
@ -173,15 +168,15 @@ public:
class CleanseCurePartyMemberMagicTrigger : public PartyMemberNeedCureTrigger
{
public:
CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC)
{
}
CleanseCurePartyMemberMagicTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse", DISPEL_MAGIC) {}
};
class HammerOfJusticeEnemyHealerTrigger : public InterruptEnemyHealerTrigger
{
public:
HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI) : InterruptEnemyHealerTrigger(botAI, "hammer of justice") {}
HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* botAI)
: InterruptEnemyHealerTrigger(botAI, "hammer of justice") {}
};
class DivineFavorTrigger : public BuffTrigger
@ -190,6 +185,14 @@ public:
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
{
public:
@ -201,7 +204,8 @@ DEBUFF_TRIGGER(AvengerShieldTrigger, "avenger's shield");
class BeaconOfLightOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "beacon of light", true) {}
BeaconOfLightOnMainTankTrigger(PlayerbotAI* ai)
: BuffOnMainTankTrigger(ai, "beacon of light", true) {}
};
class SacredShieldOnMainTankTrigger : public BuffOnMainTankTrigger
@ -213,34 +217,29 @@ public:
class BlessingOfKingsOnPartyTrigger : public BuffOnPartyTrigger
{
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
{
public:
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
{
public:
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
{
public:
BlessingOfSanctuaryOnPartyTrigger(PlayerbotAI* botAI)
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000)
{
}
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
};
class AvengingWrathTrigger : public BoostTrigger
@ -248,4 +247,5 @@ class AvengingWrathTrigger : public BoostTrigger
public:
AvengingWrathTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "avenging wrath") {}
};
#endif

View File

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