/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Trainer.h" #include "Creature.h" #include "NPCPackets.h" #include "Player.h" #include "SpellInfo.h" #include "SpellMgr.h" namespace Trainer { bool Spell::IsCastable() const { return sSpellMgr->AssertSpellInfo(SpellId)->HasEffect(SPELL_EFFECT_LEARN_SPELL); } Trainer::Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector spells) : _trainerId(trainerId), _type(type), _requirement(requirement), _spells(std::move(spells)) { _greeting[DEFAULT_LOCALE] = std::move(greeting); } void Trainer::SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const { float reputationDiscount = player->GetReputationPriceDiscount(npc); bool canLearnPrimaryProfession = player->GetFreePrimaryProfessionPoints() > 0; WorldPackets::NPC::TrainerList trainerList; trainerList.TrainerGUID = npc->GetGUID(); trainerList.TrainerType = AsUnderlyingType(_type); trainerList.TrainerID = _trainerId; trainerList.Greeting = GetGreeting(locale); trainerList.Spells.reserve(_spells.size()); for (Spell const& trainerSpell : _spells) { if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId)) continue; SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell.SpellId); bool primaryProfessionFirstRank = false; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (trainerSpellInfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL) continue; SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpellInfo->Effects[i].TriggerSpell); if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank()) primaryProfessionFirstRank = true; } trainerList.Spells.emplace_back(); WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back(); trainerListSpell.SpellID = trainerSpell.SpellId; trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell)); trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount); if (_type != Type::Class || (_type == Type::Class && player->getLevel() < trainerSpell.ReqLevel)) trainerListSpell.ReqLevel = trainerSpell.ReqLevel; trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine; trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank; trainerListSpell.ProfessionDialog = primaryProfessionFirstRank; trainerListSpell.ProfessionButton = primaryProfessionFirstRank ? canLearnPrimaryProfession : false; std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin()); } player->SendDirectMessage(trainerList.Write()); } void Trainer::TeachSpell(Creature const* npc, Player* player, uint32 spellId) const { if (!IsTrainerValidForPlayer(player)) return; Spell const* trainerSpell = GetSpell(spellId); if (!trainerSpell) { SendTeachFailure(npc, player, spellId, FailReason::Unavailable); return; } if (!CanTeachSpell(player, trainerSpell)) { SendTeachFailure(npc, player, spellId, FailReason::NotEnoughSkill); return; } float reputationDiscount = player->GetReputationPriceDiscount(npc); int32 moneyCost = int32(trainerSpell->MoneyCost * reputationDiscount); if (player->GetMoney() < uint64(moneyCost)) { SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney); return; } player->ModifyMoney(-moneyCost); npc->SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected player->SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute // learn explicitly or cast explicitly if (trainerSpell->IsCastable()) player->CastSpell(player, trainerSpell->SpellId, true); else player->LearnSpell(trainerSpell->SpellId, false); SendTeachSucceeded(npc, player, spellId); } Spell const* Trainer::GetSpell(uint32 spellId) const { auto itr = std::find_if(_spells.begin(), _spells.end(), [spellId](Spell const& trainerSpell) { return trainerSpell.SpellId == spellId; }); if (itr != _spells.end()) return &(*itr); return nullptr; } bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) const { SpellState state = GetSpellState(player, trainerSpell); if (state != SpellState::Available) return false; SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell->SpellId); if (trainerSpellInfo->IsPrimaryProfessionFirstRank() && !player->GetFreePrimaryProfessionPoints()) return false; return true; } SpellState Trainer::GetSpellState(Player const* player, Spell const* trainerSpell) const { if (player->HasSpell(trainerSpell->SpellId)) return SpellState::Known; // check race/class requirement if (!player->IsSpellFitByClassAndRace(trainerSpell->SpellId)) return SpellState::Unavailable; // check skill requirement if (trainerSpell->ReqSkillLine && player->GetBaseSkillValue(trainerSpell->ReqSkillLine) < trainerSpell->ReqSkillRank) return SpellState::Unavailable; for (int32 reqAbility : trainerSpell->ReqAbility) if (reqAbility && !player->HasSpell(reqAbility)) return SpellState::Unavailable; // check level requirement if (player->getLevel() < trainerSpell->ReqLevel) return SpellState::Unavailable; // check ranks bool hasLearnSpellEffect = false; bool knowsAllLearnedSpells = true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(trainerSpell->SpellId); if (!spellInfo || spellInfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL) continue; hasLearnSpellEffect = true; if (!player->HasSpell(spellInfo->Effects[i].TriggerSpell)) knowsAllLearnedSpells = false; if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(spellInfo->Effects[i].TriggerSpell)) if (!player->HasSpell(previousRankSpellId)) return SpellState::Unavailable; } if (!hasLearnSpellEffect) { if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(trainerSpell->SpellId)) if (!player->HasSpell(previousRankSpellId)) return SpellState::Unavailable; } else if (knowsAllLearnedSpells) return SpellState::Known; // check additional spell requirement for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId)) if (!player->HasSpell(requirePair.second)) return SpellState::Unavailable; return SpellState::Available; } bool Trainer::IsTrainerValidForPlayer(Player const* player) const { if (!GetTrainerRequirement()) return true; switch (GetTrainerType()) { case Type::Class: case Type::Pet: // check class for class trainers return player->getClass() == GetTrainerRequirement(); case Type::Mount: // check race for mount trainers return player->getRace() == GetTrainerRequirement(); case Type::Tradeskill: // check spell for profession trainers return player->HasSpell(GetTrainerRequirement()); default: break; } return true; } void Trainer::SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const { WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed; trainerBuyFailed.TrainerGUID = npc->GetGUID(); trainerBuyFailed.SpellID = spellId; trainerBuyFailed.TrainerFailedReason = AsUnderlyingType(reason); player->SendDirectMessage(trainerBuyFailed.Write()); } void Trainer::SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const { WorldPackets::NPC::TrainerBuySucceeded trainerBuySucceeded; trainerBuySucceeded.TrainerGUID = npc->GetGUID(); trainerBuySucceeded.SpellID = spellId; player->SendDirectMessage(trainerBuySucceeded.Write()); } std::string const& Trainer::GetGreeting(LocaleConstant locale) const { if (_greeting[locale].empty()) return _greeting[DEFAULT_LOCALE]; return _greeting[locale]; } void Trainer::AddGreetingLocale(LocaleConstant locale, std::string greeting) { _greeting[locale] = std::move(greeting); } }