refactor(Core/Creature): port TC handling of Trainers (#23040)

Co-authored-by: Shauren <shauren.trinity@gmail.com>
Co-authored-by: Ghaster <defscam@gmail.com>
This commit is contained in:
sogladev
2025-12-29 14:41:01 +01:00
committed by GitHub
parent 40f0c2d29b
commit ab74e7ded8
28 changed files with 8916 additions and 605 deletions

View File

@@ -81,15 +81,6 @@ std::string CreatureMovementData::ToString() const
return str.str();
}
TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const
{
TrainerSpellMap::const_iterator itr = spellList.find(spell_id);
if (itr != spellList.end())
return &itr->second;
return nullptr;
}
bool VendorItemData::RemoveItem(uint32 item_id)
{
bool found = false;
@@ -1264,52 +1255,13 @@ bool Creature::isCanInteractWithBattleMaster(Player* player, bool msg) const
return true;
}
bool Creature::isCanTrainingAndResetTalentsOf(Player* player) const
bool Creature::CanResetTalents(Player* player) const
{
return player->GetLevel() >= 10
&& GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS
&& player->IsClass((Classes)GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER);
}
bool Creature::IsValidTrainerForPlayer(Player* player, uint32* npcFlags /*= nullptr*/) const
{
if (!IsTrainer())
{
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry());
if (!trainer)
return false;
}
switch (m_creatureInfo->trainer_type)
{
case TRAINER_TYPE_CLASS:
case TRAINER_TYPE_PETS:
if (m_creatureInfo->trainer_class && !player->IsClass((Classes)m_creatureInfo->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
{
if (npcFlags)
*npcFlags &= ~UNIT_NPC_FLAG_TRAINER_CLASS;
return false;
}
break;
case TRAINER_TYPE_MOUNTS:
if (m_creatureInfo->trainer_race && m_creatureInfo->trainer_race != player->getRace())
{
return false;
}
break;
case TRAINER_TYPE_TRADESKILLS:
if (m_creatureInfo->trainer_spell && !player->HasSpell(m_creatureInfo->trainer_spell))
{
if (npcFlags)
*npcFlags &= ~UNIT_NPC_FLAG_TRAINER_PROFESSION;
return false;
}
break;
default:
break;
}
return true;
return player->GetLevel() >= 10 && trainer->IsTrainerValidForPlayer(player);
}
Player* Creature::GetLootRecipient() const
@@ -3143,11 +3095,6 @@ uint32 Creature::UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 us
return vCount->count;
}
TrainerSpellData const* Creature::GetTrainerSpells() const
{
return sObjectMgr->GetNpcTrainerSpells(GetEntry());
}
// overwrite WorldObject function for proper name localization
std::string const& Creature::GetNameForLocaleIdx(LocaleConstant loc_idx) const
{

View File

@@ -103,8 +103,7 @@ public:
///// @todo RENAME THIS!!!!!
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
bool isCanTrainingAndResetTalentsOf(Player* player) const;
[[nodiscard]] bool IsValidTrainerForPlayer(Player* player, uint32* npcFlags = nullptr) const;
bool CanResetTalents(Player* player) const;
bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const;
void LoadSpellTemplateImmunity();
bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override;
@@ -203,8 +202,6 @@ public:
uint32 GetVendorItemCurrentCount(VendorItem const* vItem);
uint32 UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count);
[[nodiscard]] TrainerSpellData const* GetTrainerSpells() const;
[[nodiscard]] CreatureTemplate const* GetCreatureTemplate() const { return m_creatureInfo; }
[[nodiscard]] CreatureData const* GetCreatureData() const { return m_creatureData; }
void SetDetectionDistance(float dist){ m_detectionDistance = dist; }

View File

@@ -215,10 +215,6 @@ struct CreatureTemplate
uint32 unit_flags2; // enum UnitFlags2 mask values
uint32 dynamicflags;
uint32 family; // enum CreatureFamily values (optional)
uint32 trainer_type;
uint32 trainer_spell;
uint32 trainer_class;
uint32 trainer_race;
uint32 type; // enum CreatureType values
uint32 type_flags; // enum CreatureTypeFlags mask values
uint32 lootid;
@@ -503,39 +499,6 @@ struct VendorItemCount
typedef std::list<VendorItemCount> VendorItemCounts;
struct TrainerSpell
{
TrainerSpell()
{
for (unsigned int & i : learnedSpell)
i = 0;
}
uint32 spell{0};
uint32 spellCost{0};
uint32 reqSkill{0};
uint32 reqSkillValue{0};
uint32 reqLevel{0};
uint32 learnedSpell[3];
uint32 reqSpell{0};
// helpers
[[nodiscard]] bool IsCastable() const { return learnedSpell[0] != spell; }
};
typedef std::unordered_map<uint32 /*spellid*/, TrainerSpell> TrainerSpellMap;
struct TrainerSpellData
{
TrainerSpellData() = default;
~TrainerSpellData() { spellList.clear(); }
TrainerSpellMap spellList;
uint32 trainerType{0}; // trainer type based at trainer spells, can be different from creature_template value.
// req. for correct show non-prof. trainers like weaponmaster, allowed values 0 and 2.
[[nodiscard]] TrainerSpell const* Find(uint32 spell_id) const;
};
struct CreatureSpellCooldown
{
CreatureSpellCooldown() = default;

View File

@@ -0,0 +1,262 @@
/*
* This file is part of the AzerothCore 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 Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 <http://www.gnu.org/licenses/>.
*/
#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<Spell> spells) : _trainerId(trainerId), _type(type), _requirement(requirement), _spells(std::move(spells))
{
_greeting[DEFAULT_LOCALE] = std::move(greeting);
}
void Trainer::SendSpells(Creature* npc, Player* player, LocaleConstant locale) const
{
float reputationDiscount = player->GetReputationPriceDiscount(npc);
WorldPackets::NPC::TrainerList trainerList;
trainerList.TrainerGUID = npc->GetGUID();
trainerList.TrainerType = AsUnderlyingType(_type);
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 (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
{
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
continue;
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.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);
trainerListSpell.PointCost[0] = 0; // spells don't cost talent points
trainerListSpell.PointCost[1] = (primaryProfessionFirstRank ? 1 : 0);
trainerListSpell.ReqLevel = trainerSpell.ReqLevel;
trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine;
trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank;
std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin());
}
player->SendDirectMessage(trainerList.Write());
}
void Trainer::TeachSpell(Creature* npc, Player* player, uint32 spellId)
{
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->HasEnoughMoney(moneyCost))
{
SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney);
return;
}
player->ModifyMoney(-moneyCost);
npc->SendPlaySpellVisual(179); // 53 SpellCastDirected
npc->SendPlaySpellImpact(player->GetGUID(), 362); // 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)
{
SpellState state = GetSpellState(player, trainerSpell);
if (state != SpellState::Available)
return false;
SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell->SpellId);
for (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
{
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
continue;
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell);
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)->GetEffects())
{
if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
continue;
hasLearnSpellEffect = true;
if (!player->HasSpell(spellEffectInfo.TriggerSpell))
knowsAllLearnedSpells = false;
if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(spellEffectInfo.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);
}
}

View File

@@ -0,0 +1,97 @@
/*
* This file is part of the AzerothCore 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 Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 <http://www.gnu.org/licenses/>.
*/
#ifndef Trainer_h__
#define Trainer_h__
#include "Common.h"
#include <array>
#include <vector>
class Creature;
class ObjectMgr;
class Player;
namespace Trainer
{
enum class Type : uint32
{
Class = 0,
Mount = 1,
Tradeskill = 2,
Pet = 3
};
enum class SpellState : uint8
{
Available = 0,
Unavailable = 1,
Known = 2
};
enum class FailReason : uint32
{
Unavailable = 0,
NotEnoughMoney = 1,
NotEnoughSkill = 2
};
struct AC_GAME_API Spell
{
uint32 SpellId = 0;
uint32 MoneyCost = 0;
uint32 ReqSkillLine = 0;
uint32 ReqSkillRank = 0;
std::array<uint32, 3> ReqAbility = { };
uint8 ReqLevel = 0;
[[nodiscard]] bool IsCastable() const;
};
class AC_GAME_API Trainer
{
public:
Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector<Spell> spells);
[[nodiscard]] Spell const* GetSpell(uint32 spellId) const;
[[nodiscard]] std::vector<Spell> const& GetSpells() const { return _spells; }
void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const;
bool CanTeachSpell(Player const* player, Spell const* trainerSpell);
void TeachSpell(Creature* npc, Player* player, uint32 spellId);
[[nodiscard]] Type GetTrainerType() const { return _type; }
[[nodiscard]] uint32 GetTrainerRequirement() const { return _requirement; }
bool IsTrainerValidForPlayer(Player const* player) const;
private:
SpellState GetSpellState(Player const* player, Spell const* trainerSpell) const;
void SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const;
void SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const;
[[nodiscard]] std::string const& GetGreeting(LocaleConstant locale) const;
friend ObjectMgr;
void AddGreetingLocale(LocaleConstant locale, std::string greeting);
uint32 _trainerId;
Type _type;
uint32 _requirement;
std::vector<Spell> _spells;
std::array<std::string, TOTAL_LOCALES> _greeting;
};
}
#endif // Trainer_h__

View File

@@ -79,6 +79,7 @@
#include "StringConvert.h"
#include "TicketMgr.h"
#include "Tokenize.h"
#include "Trainer.h"
#include "Transport.h"
#include "Unit.h"
#include "UpdateData.h"
@@ -2115,11 +2116,6 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflag
if (!creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
return nullptr;
// 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 && !IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
return nullptr;
return creature;
}
@@ -3901,74 +3897,6 @@ bool Player::HasActiveSpell(uint32 spell) const
return (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->Active && itr->second->IsInSpec(m_activeSpec));
}
TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell) const
{
if (!trainer_spell)
return TRAINER_SPELL_RED;
bool hasSpell = true;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (!trainer_spell->learnedSpell[i])
continue;
if (!HasSpell(trainer_spell->learnedSpell[i]))
{
hasSpell = false;
break;
}
}
// known spell
if (hasSpell)
return TRAINER_SPELL_GRAY;
// check skill requirement
if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue)
return TRAINER_SPELL_RED;
// check level requirement
if (GetLevel() < trainer_spell->reqLevel)
return TRAINER_SPELL_RED;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (!trainer_spell->learnedSpell[i])
continue;
// check race/class requirement
if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell[i]))
return TRAINER_SPELL_RED;
if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->learnedSpell[i]))
{
// check prev.rank requirement
if (prevSpell && !HasSpell(prevSpell))
return TRAINER_SPELL_RED;
}
SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->learnedSpell[i]);
for (SpellsRequiringSpellMap::const_iterator itr = spellsRequired.first; itr != spellsRequired.second; ++itr)
{
// check additional spell requirement
if (!HasSpell(itr->second))
return TRAINER_SPELL_RED;
}
}
// check primary prof. limit
// first rank of primary profession spell when there are no proffesions avalible is disabled
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (!trainer_spell->learnedSpell[i])
continue;
SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->learnedSpell[i]);
if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0))
return TRAINER_SPELL_GREEN_DISABLED;
}
return TRAINER_SPELL_GREEN;
}
/**
* Deletes a character from the database
*
@@ -14426,6 +14354,18 @@ bool Player::CanSeeVendor(Creature const* creature) const
return true;
}
bool Player::CanSeeTrainer(Creature const* creature) const
{
if (!creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER))
return true;
if (auto trainer = sObjectMgr->GetTrainer(creature->GetEntry()))
if (!trainer || !trainer->IsTrainerValidForPlayer(this))
return false;
return true;
}
void Player::BuildPlayerTalentsInfoData(WorldPacket* data)
{
*data << uint32(GetFreeTalentPoints()); // unspentTalentPoints

View File

@@ -210,14 +210,6 @@ struct SpellCooldown
typedef std::map<uint32, SpellCooldown> SpellCooldowns;
typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap;
enum TrainerSpellState
{
TRAINER_SPELL_GREEN = 0,
TRAINER_SPELL_RED = 1,
TRAINER_SPELL_GRAY = 2,
TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed
};
enum ActionButtonUpdateState
{
ACTIONBUTTON_UNCHANGED = 0,
@@ -1684,7 +1676,6 @@ public:
void SendRemoveControlBar();
[[nodiscard]] bool HasSpell(uint32 spell) const override;
[[nodiscard]] bool HasActiveSpell(uint32 spell) const; // show in spellbook
TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
[[nodiscard]] bool IsSpellFitByClassAndRace(uint32 spell_id) const;
bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const;
@@ -2560,6 +2551,8 @@ public:
//bool isActiveObject() const { return true; }
bool CanSeeSpellClickOn(Creature const* creature) const;
[[nodiscard]] bool CanSeeVendor(Creature const* creature) const;
[[nodiscard]] bool CanSeeTrainer(Creature const* creature) const;
private:
[[nodiscard]] bool AnyVendorOptionAvailable(uint32 menuId, Creature const* creature) const;
public:

View File

@@ -90,15 +90,15 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
}
case GOSSIP_OPTION_LEARNDUALSPEC:
case GOSSIP_OPTION_DUALSPEC_INFO:
if (!(GetSpecsCount() == 1 && creature->isCanTrainingAndResetTalentsOf(this) && !(GetLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
if (!(GetSpecsCount() == 1 && creature->CanResetTalents(this) && !(GetLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
if (!creature->isCanTrainingAndResetTalentsOf(this))
if (!creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNPETTALENTS:
if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_PETS || creature->GetCreatureTemplate()->trainer_class != CLASS_HUNTER)
if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_TAXIVENDOR:
@@ -117,11 +117,16 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
canTalk = false;
break;
case GOSSIP_OPTION_TRAINER:
if (!creature->IsValidTrainerForPlayer(this))
{
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
if (!trainer || !trainer->IsTrainerValidForPlayer(this))
{
LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (GUID: %u) requested wrong gossip menu: %u at Creature: %s (Entry: %u)",
GetName().c_str(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), creature->GetName().c_str(), creature->GetEntry());
canTalk = false;
}
break;
}
[[fallthrough]];
case GOSSIP_OPTION_GOSSIP:
if (creature->isVendorWithIconSpeak())
{
@@ -328,7 +333,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men
GetSession()->SendStablePet(guid);
break;
case GOSSIP_OPTION_TRAINER:
GetSession()->SendTrainerList(guid);
GetSession()->SendTrainerList(source->ToCreature());
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
if (GetSpecsCount() == 1 && GetLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))

View File

@@ -20953,7 +20953,7 @@ void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPoi
appendValue &= ~UNIT_NPC_FLAG_VENDOR_MASK;
}
if (!creature->IsValidTrainerForPlayer(target, &appendValue))
if (!target->CanSeeTrainer(creature))
appendValue &= ~UNIT_NPC_FLAG_TRAINER;
valuesUpdateBuf.put(posPointers.UnitNPCFlagsPos, appendValue);