From 962fdeb3d1aaaa043a8db6534fadb8d71cf3bad0 Mon Sep 17 00:00:00 2001 From: Revision Date: Mon, 5 Jan 2026 15:06:45 +0100 Subject: [PATCH] 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 --- src/RandomItemMgr.cpp | 16 +++--- src/factory/PlayerbotFactory.cpp | 63 ++++++----------------- src/strategy/actions/TrainerAction.cpp | 53 +++++++++---------- src/strategy/actions/TrainerAction.h | 5 +- src/strategy/rpg/NewRpgBaseAction.cpp | 6 ++- src/strategy/triggers/RpgTriggers.cpp | 71 +++++--------------------- src/strategy/values/BudgetValues.cpp | 29 ++++------- 7 files changed, 76 insertions(+), 167 deletions(-) diff --git a/src/RandomItemMgr.cpp b/src/RandomItemMgr.cpp index a6e143ae..87b83793 100644 --- a/src/RandomItemMgr.cpp +++ b/src/RandomItemMgr.cpp @@ -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; } } diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index ef320eba..37be9a81 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -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); } } } diff --git a/src/strategy/actions/TrainerAction.cpp b/src/strategy/actions/TrainerAction.cpp index e2e89115..cfa2f504 100644 --- a/src/strategy/actions/TrainerAction.cpp +++ b/src/strategy/actions/TrainerAction.cpp @@ -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_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); diff --git a/src/strategy/actions/TrainerAction.h b/src/strategy/actions/TrainerAction.h index 10f8a119..e6046c3a 100644 --- a/src/strategy/actions/TrainerAction.h +++ b/src/strategy/actions/TrainerAction.h @@ -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); }; diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index acd34078..94457568 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -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; diff --git a/src/strategy/triggers/RpgTriggers.cpp b/src/strategy/triggers/RpgTriggers.cpp index 90a43679..f390359b 100644 --- a/src/strategy/triggers/RpgTriggers.cpp +++ b/src/strategy/triggers/RpgTriggers.cpp @@ -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 + if (FactionTemplateEntry const* faction_template = sFactionTemplateStore.LookupEntry(cInfo->faction)) + if (pPlayer->GetReputationRank(faction_template->faction) == REP_EXALTED) + return true; + + return false; } - 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; diff --git a/src/strategy/values/BudgetValues.cpp b/src/strategy/values/BudgetValues.cpp index 6ef51f5f..daa7aca8 100644 --- a/src/strategy/values/BudgetValues.cpp +++ b/src/strategy/values/BudgetValues.cpp @@ -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); } } }