mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-28 08:06:23 +00:00
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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
262
src/server/game/Entities/Creature/Trainer.cpp
Normal file
262
src/server/game/Entities/Creature/Trainer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
97
src/server/game/Entities/Creature/Trainer.h
Normal file
97
src/server/game/Entities/Creature/Trainer.h
Normal 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__
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user