Warlock Pet Re-Summon on Strategy Change

Hello everyone,

This PR addresses issue #1464 - Now, when you change a strategy, the new pet will be summoned accordingly - as long as the warlock knows the pet. I tested this myself with level 80 warlocks, then moved to lower level warlocks. It was nice to see that when my affliction warlock went from level 29 to 30, it automatically summoned the felhunter (while having the succubus out prior).

List of changes per file:
src\AiFactory.cpp: Set default level 1 spec to demonology for altbots - it was affliction prior. This will allow them to use Curse of agony, corruption, and immolate between levels 1-10.

src\strategy\warlock\GenericWarlockNonCombatStrategy.cpp: Added the "wrong pet" trigger to each of the 5 pet strategies, coupled with the appropriate summon action.

src\strategy\warlock\WarlockAiObjectContext.cpp: Registered the "wrong pet" trigger and moved the unstable affliction static trigger all to 1 line, cleaning it up a bit.

src\strategy\warlock\WarlockTriggers.cpp: Added the logic for the wrong pet trigger here. I tried to leave detailed comments as much as possible explaining it's thought process.

src\strategy\warlock\WarlockTriggers.h: Added the WrongPetTrigger.
This commit is contained in:
ThePenguinMan96
2025-07-25 19:00:37 -07:00
parent b65646170c
commit c04477b54d
5 changed files with 83 additions and 5 deletions

View File

@@ -88,6 +88,9 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
case CLASS_PRIEST:
tab = PRIEST_TAB_HOLY;
break;
case CLASS_WARLOCK:
tab = WARLOCK_TAB_DEMONOLOGY;
break;
}
return tab;

View File

@@ -98,6 +98,7 @@ SummonImpStrategy::SummonImpStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {}
void SummonImpStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon imp", 29.0f), NULL)));
triggers.push_back(new TriggerNode("wrong pet", NextAction::array(0, new NextAction("summon imp", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Voidwalker
@@ -109,6 +110,7 @@ SummonVoidwalkerStrategy::SummonVoidwalkerStrategy(PlayerbotAI* ai) : NonCombatS
void SummonVoidwalkerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL)));
triggers.push_back(new TriggerNode("wrong pet", NextAction::array(0, new NextAction("summon voidwalker", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Succubus
@@ -120,6 +122,7 @@ SummonSuccubusStrategy::SummonSuccubusStrategy(PlayerbotAI* ai) : NonCombatStrat
void SummonSuccubusStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon succubus", 29.0f), NULL)));
triggers.push_back(new TriggerNode("wrong pet", NextAction::array(0, new NextAction("summon succubus", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Felhunter
@@ -131,6 +134,7 @@ SummonFelhunterStrategy::SummonFelhunterStrategy(PlayerbotAI* ai) : NonCombatStr
void SummonFelhunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL)));
triggers.push_back(new TriggerNode("wrong pet", NextAction::array(0, new NextAction("summon felhunter", 29.0f), NULL)));
}
// Non-combat strategy for summoning a Felguard
@@ -142,6 +146,7 @@ SummonFelguardStrategy::SummonFelguardStrategy(PlayerbotAI* ai) : NonCombatStrat
void SummonFelguardStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), NULL)));
triggers.push_back(new TriggerNode("wrong pet", NextAction::array(0, new NextAction("summon felguard", 29.0f), NULL)));
}
// Non-combat strategy for selecting themselves to receive soulstone

View File

@@ -171,6 +171,7 @@ public:
creators["curse of exhaustion"] = &WarlockTriggerFactoryInternal::curse_of_exhaustion;
creators["curse of tongues"] = &WarlockTriggerFactoryInternal::curse_of_tongues;
creators["curse of weakness"] = &WarlockTriggerFactoryInternal::curse_of_weakness;
creators["wrong pet"] = &WarlockTriggerFactoryInternal::wrong_pet;
}
private:
@@ -197,10 +198,7 @@ private:
static Trigger* immolate(PlayerbotAI* botAI) { return new ImmolateTrigger(botAI); }
static Trigger* immolate_on_attacker(PlayerbotAI* ai) { return new ImmolateOnAttackerTrigger(ai); }
static Trigger* unstable_affliction(PlayerbotAI* ai) { return new UnstableAfflictionTrigger(ai); }
static Trigger* unstable_affliction_on_attacker(PlayerbotAI* ai)
{
return new UnstableAfflictionOnAttackerTrigger(ai);
}
static Trigger* unstable_affliction_on_attacker(PlayerbotAI* ai) { return new UnstableAfflictionOnAttackerTrigger(ai); }
static Trigger* haunt(PlayerbotAI* ai) { return new HauntTrigger(ai); }
static Trigger* decimation(PlayerbotAI* ai) { return new DecimationTrigger(ai); }
static Trigger* life_tap(PlayerbotAI* ai) { return new LifeTapTrigger(ai); }
@@ -218,6 +216,7 @@ private:
static Trigger* curse_of_exhaustion(PlayerbotAI* ai) { return new CurseOfExhaustionTrigger(ai); }
static Trigger* curse_of_tongues(PlayerbotAI* ai) { return new CurseOfTonguesTrigger(ai); }
static Trigger* curse_of_weakness(PlayerbotAI* ai) { return new CurseOfWeaknessTrigger(ai); }
static Trigger* wrong_pet(PlayerbotAI* ai) { return new WrongPetTrigger(ai); }
};
class WarlockAiObjectContextInternal : public NamedObjectContext<Action>
@@ -413,4 +412,4 @@ void WarlockAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContext
void WarlockAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
AiObjectContext::BuildSharedValueContexts(valueContexts);
}
}

View File

@@ -165,3 +165,66 @@ bool CurseOfWeaknessTrigger::IsActive()
// Use default BuffTrigger logic for the rest (only trigger if debuff is missing or expiring)
return BuffTrigger::IsActive();
}
struct WarlockPetDef
{
const char* strategy; // The strategy string as recognized by the AI (e.g., "imp", "voidwalker", etc.)
uint32 spellId; // The spell ID required to summon this pet
uint32 npcEntry; // The NPC entry ID for the summoned pet creature
};
// Static array with all relevant Warlock pets and their data
static const WarlockPetDef pets[] = {{"imp", 688, 416},
{"voidwalker", 697, 1860},
{"succubus", 712, 1863},
{"felhunter", 691, 417},
{"felguard", 30146, 17252}};
bool WrongPetTrigger::IsActive()
{
// Retrieve the bot player and its current pet (if any)
Player* bot = botAI->GetBot();
Pet* pet = bot->GetPet();
// Step 1: Count how many pet strategies are currently enabled for this bot.
// While doing so, also remember which pet strategy is the only enabled one (if that's the case).
int enabledCount = 0;
const WarlockPetDef* enabledPet =
nullptr; // Pointer to the pet definition of the enabled strategy, if only one is enabled
for (const WarlockPetDef& pd : pets)
{
if (botAI->HasStrategy(pd.strategy, BOT_STATE_NON_COMBAT))
{
enabledCount++;
enabledPet = &pd; // Save the pointer to last enabled pet
}
}
// Step 2: If not exactly one pet strategy is enabled, we should not trigger.
// This prevents ambiguous or conflicting situations.
if (enabledCount != 1)
return false;
// Step 3: At this point, we know only one pet strategy is enabled.
// We check if the currently summoned pet matches the enabled strategy.
bool correctPet = false;
if (pet)
{
CreatureTemplate const* ct = pet->GetCreatureTemplate();
// Check if the pet's NPC entry matches the expected one for the enabled strategy
if (ct && ct->Entry == enabledPet->npcEntry)
correctPet = true;
}
// Step 4: If the correct pet is already summoned, the trigger should not activate.
if (correctPet)
return false;
// Step 5: Finally, check if the bot actually knows the spell to summon the desired pet.
// If so, the trigger is active (bot should summon the correct pet).
if (bot->HasSpell(enabledPet->spellId))
return true;
// Step 6: If we get here, the bot doesn't know the spell required to support the active pet strategy
return false;
}

View File

@@ -112,6 +112,14 @@ public:
HasHealthstoneTrigger(PlayerbotAI* botAI) : WarlockConjuredItemTrigger(botAI, "healthstone") {}
};
class WrongPetTrigger : public Trigger
{
public:
WrongPetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "wrong pet") {}
bool IsActive() override;
};
// CC and Pet Triggers
class BanishTrigger : public HasCcTargetTrigger