Updated to support latest master (#1965)

This needs extensive testing.

What's important is spells given to bots. Class spells, mounts,
professions etc. Make sure they get the spells they should, when they
should.

Requires https://github.com/mod-playerbots/azerothcore-wotlk/pull/132
This commit is contained in:
Revision
2026-01-05 15:06:45 +01:00
committed by GitHub
parent 83c6977de5
commit 962fdeb3d1
7 changed files with 76 additions and 167 deletions

View File

@@ -2834,22 +2834,20 @@ inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
if (!trainer)
continue;
uint32 trainerId = itr->second.Entry;
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill)
continue;
for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
iter != trainer_spells->spellList.end(); ++iter)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &iter->second;
if (!tSpell || tSpell->reqSkill != skillId)
if (spell.ReqSkillLine != skillId)
continue;
if (IsCraftedBy(proto, tSpell->spell))
if (IsCraftedBy(proto, spell.SpellId))
return true;
}
}

View File

@@ -2526,66 +2526,35 @@ void PlayerbotFactory::InitAvailableSpells()
for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin();
i != creatureTemplateContainer->end(); ++i)
{
CreatureTemplate const& co = i->second;
if (co.trainer_type != TRAINER_TYPE_TRADESKILLS && co.trainer_type != TRAINER_TYPE_CLASS)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(i->first);
if (!trainer)
continue;
if (co.trainer_type == TRAINER_TYPE_CLASS && co.trainer_class != bot->getClass())
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill &&
trainer->GetTrainerType() != Trainer::Type::Class)
continue;
uint32 trainerId = co.Entry;
trainerIdCache[bot->getClass()].push_back(trainerId);
if (trainer->GetTrainerType() == Trainer::Type::Class &&
!trainer->IsTrainerValidForPlayer(bot))
continue;
trainerIdCache[bot->getClass()].push_back(i->first);
}
}
for (uint32 trainerId : trainerIdCache[bot->getClass()])
{
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(trainerId);
if (!trainer_spells)
continue;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0]))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
bool learn = true;
for (uint8 j = 0; j < 3; ++j)
{
if (!tSpell->learnedSpell[j] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[j]))
continue;
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_PROFICIENCY ||
(spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP &&
spellInfo->Effects[j].MiscValue != SKILL_RIDING) ||
spellInfo->Effects[j].Effect == SPELL_EFFECT_DUAL_WIELD)
{
learn = false;
break;
}
}
if (!learn)
{
continue;
}
if (tSpell->IsCastable())
bot->CastSpell(bot, tSpell->spell, true);
if (spell.IsCastable())
bot->CastSpell(bot, spell.SpellId, true);
else
bot->learnSpell(tSpell->learnedSpell[0], false);
bot->learnSpell(spell.SpellId, false);
}
}
}

View File

@@ -10,7 +10,7 @@
#include "PlayerbotFactory.h"
#include "Playerbots.h"
void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg)
void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg)
{
if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold))
{
@@ -23,7 +23,7 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
bot->ModifyMoney(-int32(cost));
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId);
if (!spellInfo)
return;
@@ -41,10 +41,8 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
}
}
if (!learned && !bot->HasSpell(tSpell->spell))
{
bot->learnSpell(tSpell->spell);
}
if (!learned && !bot->HasSpell(tSpell.SpellId))
bot->learnSpell(tSpell.SpellId);
msg << " - learned";
}
@@ -53,37 +51,35 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell
{
TellHeader(creature);
TrainerSpellData const* trainer_spells = creature->GetTrainerSpells();
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
if (!trainer)
return;
float fDiscountMod = bot->GetReputationPriceDiscount(creature);
uint32 totalCost = 0;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
if (!spells.empty() && spells.find(spell.SpellId) == spells.end())
continue;
uint32 spellId = tSpell->spell;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
if (!spellInfo)
continue;
if (!spells.empty() && spells.find(tSpell->spell) == spells.end())
continue;
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod));
totalCost += cost;
std::ostringstream out;
out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost);
if (action)
(this->*action)(cost, tSpell, out);
(this->*action)(cost, spell, out);
botAI->TellMaster(out);
}
@@ -112,15 +108,14 @@ bool TrainerAction::Execute(Event event)
if (!creature || !creature->IsTrainer())
return false;
if (!creature->IsValidTrainerForPlayer(bot))
{
botAI->TellError("This trainer cannot teach me");
return false;
}
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
// check present spell in trainer spell list
TrainerSpellData const* cSpells = creature->GetTrainerSpells();
if (!cSpells)
if (!trainer || !trainer->IsTrainerValidForPlayer(bot))
return false;
std::vector<Trainer::Spell> trainer_spells = trainer->GetSpells();
if (trainer_spells.empty())
{
botAI->TellError("No spells can be learned from this trainer");
return false;
@@ -133,7 +128,7 @@ bool TrainerAction::Execute(Event event)
if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) ||
(sPlayerbotAIConfig->autoTrainSpells != "no" &&
(creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS ||
(trainer->GetTrainerType() != Trainer::Type::Tradeskill ||
!botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make
// config dependent.
Iterate(creature, &TrainerAction::Learn, spells);

View File

@@ -8,6 +8,7 @@
#include "Action.h"
#include "ChatHelper.h"
#include "Trainer.h"
class Creature;
class PlayerbotAI;
@@ -22,9 +23,9 @@ public:
bool Execute(Event event) override;
private:
typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, std::ostringstream& msg);
typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg);
void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells);
void Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg);
void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg);
void TellHeader(Creature* creature);
void TellFooter(uint32 totalCost);
};

View File

@@ -302,12 +302,14 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver)
if (creature->GetReactionTo(bot) <= REP_UNFRIENDLY)
return false;
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
// pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train
// dual wield to a shaman :/ too many to change in sql and watch in the future pussywizard: this function is
// not used when talking, but when already taking action (buy spell, reset talents, show spell list)
if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) &&
creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS &&
!bot->IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
trainer->GetTrainerType() == Trainer::Type::Class &&
!trainer->IsTrainerValidForPlayer(bot))
return false;
return true;

View File

@@ -163,43 +163,18 @@ bool RpgRepairTrigger::IsActive()
bool RpgTrainTrigger::IsTrainerOf(CreatureTemplate const* cInfo, Player* pPlayer)
{
switch (cInfo->trainer_type)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cInfo->Entry);
if (trainer->GetTrainerType() == Trainer::Type::Mount && trainer->GetTrainerRequirement() != pPlayer->getRace())
{
case TRAINER_TYPE_CLASS:
if (pPlayer->getClass() != cInfo->trainer_class)
{
return false;
}
break;
case TRAINER_TYPE_PETS:
if (pPlayer->getClass() != CLASS_HUNTER)
{
return false;
}
break;
case TRAINER_TYPE_MOUNTS:
if (cInfo->trainer_race && pPlayer->getRace() != cInfo->trainer_race)
{
// Allowed to train if exalted
if (FactionTemplateEntry const* faction_template = sFactionTemplateStore.LookupEntry(cInfo->faction))
{
if (pPlayer->GetReputationRank(faction_template->faction) == REP_EXALTED)
return true;
}
return false;
}
break;
case TRAINER_TYPE_TRADESKILLS:
if (cInfo->trainer_spell && !pPlayer->HasSpell(cInfo->trainer_spell))
{
return false;
}
break;
default:
return false; // checked and error output at creature_template loading
}
return true;
return trainer->IsTrainerValidForPlayer(pPlayer);
}
bool RpgTrainTrigger::IsActive()
@@ -214,37 +189,17 @@ bool RpgTrainTrigger::IsActive()
if (!IsTrainerOf(cInfo, bot))
return false;
// check present spell in trainer spell list
TrainerSpellData const* cSpells = sObjectMgr->GetNpcTrainerSpells(guidP.GetEntry());
if (!cSpells)
{
return false;
}
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cInfo->Entry);
FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction);
float fDiscountMod = bot->GetReputationPriceDiscount(factionTemplate);
TrainerSpellMap trainer_spells;
if (cSpells)
trainer_spells.insert(cSpells->spellList.begin(), cSpells->spellList.end());
for (TrainerSpellMap::const_iterator itr = trainer_spells.begin(); itr != trainer_spells.end(); ++itr)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
continue;
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod));
uint32 spellId = tSpell->spell;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
if (cost > AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells))
continue;

View File

@@ -102,35 +102,24 @@ uint32 TrainCostValue::Calculate()
{
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
if (itr->second.trainer_type != TRAINER_TYPE_CLASS && itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
if (!trainer)
continue;
if (itr->second.trainer_type == TRAINER_TYPE_CLASS && itr->second.trainer_class != bot->getClass())
if (trainer->GetTrainerType() != Trainer::Type::Class || !trainer->IsTrainerValidForPlayer(bot))
continue;
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(itr->first);
if (!trainer_spells)
continue;
for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
iter != trainer_spells->spellList.end(); ++iter)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &iter->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
if (spells.find(spell.SpellId) != spells.end())
continue;
if (itr->second.trainer_type == TRAINER_TYPE_TRADESKILLS)
continue;
if (spells.find(tSpell->spell) != spells.end())
continue;
TotalCost += tSpell->spellCost;
spells.insert(tSpell->spell);
TotalCost += spell.MoneyCost;
spells.insert(spell.SpellId);
}
}
}