Files
mod-playerbots/src/strategy/actions/AutoMaintenanceOnLevelupAction.cpp
privatecore dc55ecfd9c Fix wrong logic when processing quest reward spells during maintenance level-up action (#2001)
The previous behavior failed cuz if a spell has no
`SPELL_EFFECT_LEARN_SPELL`, bots learned the original spell from
`quest_template` rewards w/o any checks, when they should never learn
any spells specified in the `RewardSpell` and/or `RewardDisplaySpell`.

PR: https://github.com/mod-playerbots/mod-playerbots/pull/1996 Thx
@Wishmaster117 for the collaboration!

Fix issue https://github.com/mod-playerbots/mod-playerbots/issues/1759
2026-01-10 01:31:54 +01:00

188 lines
5.7 KiB
C++

#include "AutoMaintenanceOnLevelupAction.h"
#include "GuildMgr.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "BroadcastHelper.h"
bool AutoMaintenanceOnLevelupAction::Execute(Event event)
{
AutoPickTalents();
AutoLearnSpell();
AutoUpgradeEquip();
AutoTeleportForLevel();
return true;
}
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{
if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot))
{
return;
}
if (botAI->HasRealPlayerMaster())
{
return;
}
sRandomPlayerbotMgr->RandomTeleportForLevel(bot);
return;
}
void AutoMaintenanceOnLevelupAction::AutoPickTalents()
{
if (!sPlayerbotAIConfig->autoPickTalents || !sRandomPlayerbotMgr->IsRandomBot(bot))
return;
if (bot->GetFreeTalentPoints() <= 0)
return;
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitTalentsTree(true, true, true);
factory.InitPetTalents();
}
void AutoMaintenanceOnLevelupAction::AutoLearnSpell()
{
std::ostringstream out;
LearnSpells(&out);
if (!out.str().empty())
{
std::string const temp = out.str();
out.seekp(0);
out << "Learned spells: ";
out << temp;
out.seekp(-2, out.cur);
out << ".";
botAI->TellMaster(out);
}
return;
}
void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
{
BroadcastHelper::BroadcastLevelup(botAI, bot);
if (sPlayerbotAIConfig->autoLearnTrainerSpells && sRandomPlayerbotMgr->IsRandomBot(bot))
LearnTrainerSpells(out);
if (sPlayerbotAIConfig->autoLearnQuestSpells && sRandomPlayerbotMgr->IsRandomBot(bot))
LearnQuestSpells(out);
}
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out)
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitClassSpells();
factory.InitAvailableSpells();
factory.InitSkills();
factory.InitPet();
}
void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
{
// CreatureTemplate const* co = sCreatureStorage.LookupEntry<CreatureTemplate>(id);
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
{
//uint32 questId = i->first; //not used, line marked for removal.
Quest const* quest = i->second;
// only process class-specific quests to learn class-related spells, cuz
// we don't want all these bunch of entries to be handled!
if (!quest->GetRequiredClasses())
continue;
// skip quests that are repeatable, too low level, or above bots' level
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
continue;
// skip if bot doesnt satisfy class, race, or skill requirements
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
!bot->SatisfyQuestSkill(quest, false))
continue;
if (quest->GetRewSpellCast() > 0) // RewardSpell - expected route
{
LearnSpell(quest->GetRewSpellCast(), out);
}
else if (quest->GetRewSpell() > 0) // RewardDisplaySpell - fallback
{
LearnSpell(quest->GetRewSpell(), out);
}
}
}
std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* sInfo)
{
std::ostringstream out;
std::string const rank = sInfo->Rank[0];
if (rank.empty())
out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[LOCALE_enUS] << "]|h|r";
else
out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[LOCALE_enUS] << " " << rank << "]|h|r";
return out.str();
}
void AutoMaintenanceOnLevelupAction::LearnSpell(uint32 spellId, std::ostringstream* out)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return;
SpellInfo const* triggeredInfo;
// find effect with learn spell and check if we have this spell
bool found = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
{
triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell);
// do not learn profession specialties!
if (!triggeredInfo || triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
break;
found = true;
break;
}
}
if (!found)
return;
// NOTE: When rewarding quests, core casts spells instead of learning them,
// but we sacrifice safe cast checks here in favor of performance/speed
bot->learnSpell(triggeredInfo->Id);
*out << FormatSpell(triggeredInfo) << ", ";
}
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
{
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
return;
PlayerbotFactory factory(bot, bot->GetLevel());
// Clean up old consumables before adding new ones
factory.CleanupConsumables();
factory.InitAmmo();
factory.InitReagents();
factory.InitFood();
factory.InitConsumables();
factory.InitPotions();
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
{
if (sPlayerbotAIConfig->incrementalGearInit)
factory.InitEquipment(true);
}
}