/* * 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 "BattlePetMgr.h" #include "ConditionMgr.h" #include "Creature.h" #include "Log.h" #include "NPCPackets.h" #include "Player.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "WorldSession.h" namespace Trainer { bool Spell::IsCastable() const { return sSpellMgr->AssertSpellInfo(SpellId, DIFFICULTY_NONE)->HasEffect(SPELL_EFFECT_LEARN_SPELL); } Trainer::Trainer(uint32 id, Type type, std::string_view greeting, std::vector spells) : _id(id), _type(type), _spells(std::move(spells)) { _greeting[DEFAULT_LOCALE] = greeting; } void Trainer::SendSpells(Creature const* npc, Player* player, LocaleConstant locale) const { float reputationDiscount = player->GetReputationPriceDiscount(npc); WorldPackets::NPC::TrainerList trainerList; trainerList.TrainerGUID = npc->GetGUID(); trainerList.TrainerType = AsUnderlyingType(_type); trainerList.TrainerID = _id; trainerList.Greeting = GetGreeting(locale); trainerList.Spells.reserve(_spells.size()); for (Spell const& trainerSpell : _spells) { if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId)) continue; if (!sConditionMgr->IsObjectMeetingTrainerSpellConditions(_id, trainerSpell.SpellId, player)) { TC_LOG_DEBUG("condition", "SendSpells: conditions not met for trainer id {} spell {} player '{}' ({})", _id, trainerSpell.SpellId, player->GetName(), player->GetGUID().ToString()); continue; } trainerList.Spells.emplace_back(); WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back(); trainerListSpell.SpellID = trainerSpell.SpellId; trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount); trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine; trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank; std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin()); trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell)); trainerListSpell.ReqLevel = trainerSpell.ReqLevel; } player->SendDirectMessage(trainerList.Write()); } void Trainer::TeachSpell(Creature const* npc, Player* player, uint32 spellId) const { Spell const* trainerSpell = GetSpell(spellId); if (!trainerSpell || !CanTeachSpell(player, trainerSpell)) { SendTeachFailure(npc, player, spellId, FailReason::Unavailable); return; } bool sendSpellVisual = true; BattlePetSpeciesEntry const* speciesEntry = BattlePets::BattlePetMgr::GetBattlePetSpeciesBySpell(trainerSpell->SpellId); if (speciesEntry) { if (player->GetSession()->GetBattlePetMgr()->HasMaxPetCount(speciesEntry, player->GetGUID())) { // Don't send any error to client (intended) return; } sendSpellVisual = false; } float reputationDiscount = player->GetReputationPriceDiscount(npc); int64 moneyCost = int64(trainerSpell->MoneyCost * reputationDiscount); if (!player->HasEnoughMoney(moneyCost)) { SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney); return; } player->ModifyMoney(-moneyCost); if (sendSpellVisual) { 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 { bool dependent = false; if (speciesEntry) { player->GetSession()->GetBattlePetMgr()->AddPet(speciesEntry->ID, BattlePets::BattlePetMgr::SelectPetDisplay(speciesEntry), BattlePets::BattlePetMgr::RollPetBreed(speciesEntry->ID), BattlePets::BattlePetMgr::GetDefaultPetQuality(speciesEntry->ID)); // If the spell summons a battle pet, we fake that it has been learned and the battle pet is added // marking as dependent prevents saving the spell to database (intended) dependent = true; } player->LearnSpell(trainerSpell->SpellId, dependent); } } 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, DIFFICULTY_NONE); if (trainerSpellInfo->IsPrimaryProfessionFirstRank() && !player->GetFreePrimaryProfessionPoints()) return false; for (SpellEffectInfo const& effect : trainerSpellInfo->GetEffects()) { if (!effect.IsEffect(SPELL_EFFECT_LEARN_SPELL)) continue; SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(effect.TriggerSpell, DIFFICULTY_NONE); if (learnedSpellInfo && learnedSpellInfo->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 (SpellEffectInfo const& spellEffectInfo : sSpellMgr->AssertSpellInfo(trainerSpell->SpellId, DIFFICULTY_NONE)->GetEffects()) { if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL)) continue; hasLearnSpellEffect = true; if (!player->HasSpell(spellEffectInfo.TriggerSpell)) knowsAllLearnedSpells = false; } if (hasLearnSpellEffect && knowsAllLearnedSpells) return SpellState::Known; return SpellState::Available; } 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()); } 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_view greeting) { _greeting[locale] = greeting; } }