diff --git a/data/sql/updates/pending_db_world/rev_1767486855333599800.sql b/data/sql/updates/pending_db_world/rev_1767486855333599800.sql new file mode 100644 index 000000000..66224d0b5 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1767486855333599800.sql @@ -0,0 +1,8 @@ +-- +DELETE FROM `command` WHERE `name`='learn all my quest'; +DELETE FROM `command` WHERE `name`='learn all my trainer'; +DELETE FROM `command` WHERE `name`='learn all my spells'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('learn all my quest', 2, 'Syntax: .learn all my quest Learn all spells rewarded from quest for your class.'), +('learn all my trainer', 2, 'Syntax: .learn all my trainer Learn all spells taught by trainers for your class.'); +UPDATE `command` SET `help` = 'Syntax: .learn all my class Learn all spells (trainer, talent, and quest rewards) for your class.' WHERE `name`='learn all my class'; diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp index c9d3d193c..58b61aabb 100644 --- a/src/server/game/Entities/Creature/Trainer.cpp +++ b/src/server/game/Entities/Creature/Trainer.cpp @@ -130,7 +130,7 @@ namespace Trainer return nullptr; } - bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) + bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) const { SpellState state = GetSpellState(player, trainerSpell); if (state != SpellState::Available) diff --git a/src/server/game/Entities/Creature/Trainer.h b/src/server/game/Entities/Creature/Trainer.h index 66de1154d..49e405603 100644 --- a/src/server/game/Entities/Creature/Trainer.h +++ b/src/server/game/Entities/Creature/Trainer.h @@ -70,7 +70,7 @@ namespace Trainer [[nodiscard]] Spell const* GetSpell(uint32 spellId) const; [[nodiscard]] std::vector const& GetSpells() const { return _spells; } void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const; - bool CanTeachSpell(Player const* player, Spell const* trainerSpell); + bool CanTeachSpell(Player const* player, Spell const* trainerSpell) const; void TeachSpell(Creature* npc, Player* player, uint32 spellId); [[nodiscard]] Type GetTrainerType() const { return _type; } diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index b3bcd7fac..fe9ccbce6 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -9719,6 +9719,7 @@ void ObjectMgr::LoadTrainers() // For reload case _trainers.clear(); + _classTrainers.clear(); std::unordered_map> spellsByTrainer; if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell")) @@ -9795,7 +9796,18 @@ void ObjectMgr::LoadTrainers() spellsByTrainer.erase(spellsItr); } - _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells))); + auto [it, isNew] = _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells))); + ASSERT(isNew); + if (trainerType == Trainer::Type::Class) + { + if (!requirement || requirement >= MAX_CLASSES) + LOG_ERROR("sql.sql", "Table `trainer` has invalid class requirement for trainer {}, ignoring"); + else + { + uint8 classId = static_cast(requirement); + _classTrainers[classId].push_back(&it->second); + } + } } while (trainersResult->NextRow()); } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index d0b93880a..1c6185a50 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1418,6 +1418,7 @@ public: bool DeleteGameTele(std::string_view name); Trainer::Trainer* GetTrainer(uint32 creatureId); + std::vector const& GetClassTrainers(uint8 classId) const { return _classTrainers.at(classId); } [[nodiscard]] VendorItemData const* GetNpcVendorItemList(uint32 entry) const { @@ -1666,6 +1667,7 @@ private: CacheVendorItemContainer _cacheVendorItemStore; std::unordered_map _trainers; + std::unordered_map> _classTrainers; std::unordered_map _creatureDefaultTrainers; std::set _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index 1d1825fa8..1b5cc4b89 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -37,8 +37,9 @@ public: { { "class", HandleLearnAllMyClassCommand, SEC_GAMEMASTER, Console::No }, { "pettalents", HandleLearnAllMyPetTalentsCommand, SEC_GAMEMASTER, Console::No }, - { "spells", HandleLearnAllMySpellsCommand, SEC_GAMEMASTER, Console::No }, - { "talents", HandleLearnAllMyTalentsCommand, SEC_GAMEMASTER, Console::No } + { "trainer", HandleLearnAllMyTrainerSpellsCommand, SEC_GAMEMASTER, Console::No }, + { "talents", HandleLearnAllMyTalentsCommand, SEC_GAMEMASTER, Console::No }, + { "quest", HandleLearnAllMyQuestSpells, SEC_GAMEMASTER, Console::No } }; static ChatCommandTable learnAllCommandTable = @@ -98,52 +99,57 @@ public: static bool HandleLearnAllMyClassCommand(ChatHandler* handler) { - HandleLearnAllMySpellsCommand(handler); + HandleLearnAllMyTrainerSpellsCommand(handler); HandleLearnAllMyTalentsCommand(handler); + HandleLearnAllMyQuestSpells(handler); return true; } - static bool HandleLearnAllMySpellsCommand(ChatHandler* handler) + static bool HandleLearnAllMyQuestSpells(ChatHandler* handler) { - ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(handler->GetSession()->GetPlayer()->getClass()); - if (!classEntry) - return true; - uint32 family = classEntry->spellfamily; - - for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i) + Player* player = handler->GetPlayer(); + for (auto const& questPair : sObjectMgr->GetQuestTemplates()) { - SkillLineAbilityEntry const* entry = sSkillLineAbilityStore.LookupEntry(i); - if (!entry) - continue; - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry->Spell); - if (!spellInfo) - continue; - - // skip server-side/triggered spells - if (spellInfo->SpellLevel == 0) - continue; - - // skip wrong class/race skills - if (!handler->GetSession()->GetPlayer()->IsSpellFitByClassAndRace(spellInfo->Id)) - continue; - - // skip other spell families - if (spellInfo->SpellFamilyName != family) - continue; - - // skip spells with first rank learned as talent (and all talents then also) - uint32 firstRank = sSpellMgr->GetFirstSpellInChain(spellInfo->Id); - if (GetTalentSpellCost(firstRank) > 0) - continue; - - // skip broken spells - if (!SpellMgr::IsSpellValid(spellInfo)) - continue; - - handler->GetSession()->GetPlayer()->learnSpell(spellInfo->Id); + Quest const* quest = questPair.second; + if (quest->GetRequiredClasses() && player->SatisfyQuestClass(quest, false)) + player->learnQuestRewardedSpells(quest); } + return true; + } + + static bool HandleLearnAllMyTrainerSpellsCommand(ChatHandler* handler) + { + if (!sChrClassesStore.LookupEntry(handler->GetSession()->GetPlayer()->getClass())) + return true; + + Player* player = handler->GetPlayer(); + std::vector const& trainers = sObjectMgr->GetClassTrainers(player->getClass()); + + bool hadNew; + do + { + hadNew = false; + for (Trainer::Trainer const* trainer : trainers) + { + if (!trainer->IsTrainerValidForPlayer(player)) + continue; + + for (Trainer::Spell const& trainerSpell : trainer->GetSpells()) + { + if (!trainer->CanTeachSpell(player, &trainerSpell)) + continue; + + if (trainerSpell.IsCastable()) + player->CastSpell(player, trainerSpell.SpellId, true); + else + player->learnSpell(trainerSpell.SpellId, false); + + hadNew = true; + } + } + } while (hadNew); + handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_SPELLS); return true; }