aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/database/Database/Implementation/WorldDatabase.cpp2
-rw-r--r--src/server/game/Accounts/RBAC.h2
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp24
-rw-r--r--src/server/game/Entities/Creature/Creature.h4
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h36
-rw-r--r--src/server/game/Entities/Creature/Trainer.cpp243
-rw-r--r--src/server/game/Entities/Creature/Trainer.h96
-rw-r--r--src/server/game/Entities/Object/Object.cpp6
-rw-r--r--src/server/game/Entities/Object/Object.h6
-rw-r--r--src/server/game/Entities/Player/Player.cpp92
-rw-r--r--src/server/game/Entities/Player/Player.h17
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp4
-rw-r--r--src/server/game/Entities/Unit/Unit.h4
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp340
-rw-r--r--src/server/game/Globals/ObjectMgr.h18
-rw-r--r--src/server/game/Grids/Notifiers/GridNotifiers.h6
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp183
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp2
-rw-r--r--src/server/game/Server/Packets/AllPackets.h1
-rw-r--r--src/server/game/Server/Packets/NPCPackets.cpp70
-rw-r--r--src/server/game/Server/Packets/NPCPackets.h107
-rw-r--r--src/server/game/Server/WorldSession.h12
-rw-r--r--src/server/game/Spells/SpellMgr.cpp4
-rw-r--r--src/server/game/Spells/SpellMgr.h3
-rw-r--r--src/server/game/World/World.cpp11
-rw-r--r--src/server/scripts/Commands/cs_reload.cpp16
-rw-r--r--src/server/scripts/Northrend/zone_storm_peaks.cpp2
-rw-r--r--src/server/scripts/World/npc_professions.cpp6
-rw-r--r--src/server/shared/SharedDefines.h10
29 files changed, 790 insertions, 537 deletions
diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp
index 085b2a5f283..5cc0c92d7aa 100644
--- a/src/server/database/Database/Implementation/WorldDatabase.cpp
+++ b/src/server/database/Database/Implementation/WorldDatabase.cpp
@@ -78,7 +78,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, permission, help FROM command", CONNECTION_SYNCH);
- PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
+ PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH);
diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h
index cbf0dec3f64..c9dfc573e1e 100644
--- a/src/server/game/Accounts/RBAC.h
+++ b/src/server/game/Accounts/RBAC.h
@@ -569,7 +569,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_RELOAD_MAIL_LOOT_TEMPLATE = 669,
RBAC_PERM_COMMAND_RELOAD_MILLING_LOOT_TEMPLATE = 670,
RBAC_PERM_COMMAND_RELOAD_NPC_SPELLCLICK_SPELLS = 671,
- RBAC_PERM_COMMAND_RELOAD_NPC_TRAINER = 672,
+ RBAC_PERM_COMMAND_RELOAD_TRAINER = 672,
RBAC_PERM_COMMAND_RELOAD_NPC_VENDOR = 673,
RBAC_PERM_COMMAND_RELOAD_PAGE_TEXT = 674,
RBAC_PERM_COMMAND_RELOAD_PICKPOCKETING_LOOT_TEMPLATE = 675,
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index cf0cb59a91a..da4bc617fea 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -72,15 +72,6 @@ std::string CreatureMovementData::ToString() const
VendorItemCount::VendorItemCount(uint32 _item, uint32 _count)
: itemId(_item), count(_count), lastIncrementTime(GameTime::GetGameTime()) { }
-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 VendorItem::IsGoldRequired(ItemTemplate const* pProto) const
{
return (pProto->Flags2 & ITEM_FLAG2_DONT_IGNORE_BUY_PRICE) || !ExtendedCost;
@@ -1191,11 +1182,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->getClass() == GetCreatureTemplate()->trainer_class;
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry());
+ if (!trainer)
+ return false;
+
+ return player->getLevel() >= 10 && trainer->IsTrainerValidForPlayer(player);
}
Player* Creature::GetLootRecipient() const
@@ -2706,11 +2699,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
{
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 05e2e32c6ef..48d3a89012d 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -118,7 +118,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
/// @todo Rename these properly
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
- bool isCanTrainingAndResetTalentsOf(Player* player) const;
+ bool CanResetTalents(Player* player) const;
bool CanCreatureAttack(Unit const* victim, bool force = true) const;
void LoadTemplateImmunities();
bool IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster) const override;
@@ -177,8 +177,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
uint32 GetVendorItemCurrentCount(VendorItem const* vItem);
uint32 UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count);
- TrainerSpellData const* GetTrainerSpells() const;
-
CreatureTemplate const* GetCreatureTemplate() const { return m_creatureInfo; }
CreatureData const* GetCreatureData() const { return m_creatureData; }
CreatureAddon const* GetCreatureAddon() const;
diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h
index 29100324590..86f8f211d32 100644
--- a/src/server/game/Entities/Creature/CreatureData.h
+++ b/src/server/game/Entities/Creature/CreatureData.h
@@ -151,10 +151,6 @@ struct TC_GAME_API CreatureTemplate
uint32 unit_flags2; // enum UnitFlags2 mask values
uint32 dynamicflags;
CreatureFamily 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;
@@ -358,36 +354,4 @@ struct VendorItemData
}
};
-struct TrainerSpell
-{
- TrainerSpell() : SpellID(0), MoneyCost(0), ReqSkillLine(0), ReqSkillRank(0), ReqLevel(0)
- {
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- ReqAbility[i] = 0;
- }
-
- uint32 SpellID;
- uint32 MoneyCost;
- uint32 ReqSkillLine;
- uint32 ReqSkillRank;
- uint32 ReqLevel;
- uint32 ReqAbility[3];
-
- // helpers
- bool IsCastable() const { return ReqAbility[0] != SpellID; }
-};
-
-typedef std::unordered_map<uint32 /*spellid*/, TrainerSpell> TrainerSpellMap;
-
-struct TC_GAME_API TrainerSpellData
-{
- TrainerSpellData() : trainerType(0) { }
- ~TrainerSpellData() { spellList.clear(); }
-
- TrainerSpellMap spellList;
- uint32 trainerType; // 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.
- TrainerSpell const* Find(uint32 spell_id) const;
-};
-
#endif // CreatureData_h__
diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp
new file mode 100644
index 00000000000..79e496ce3bc
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 <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 const* npc, Player const* 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;
+
+ bool primaryProfessionFirstRank = false;
+ for (int32 reqAbility : trainerSpell.ReqAbility)
+ {
+ SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(reqAbility);
+ 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.ProfessionDialog = (primaryProfessionFirstRank && (player->GetFreePrimaryProfessionPoints() > 0) ? 1 : 0);
+ trainerListSpell.ProfessionButton = (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 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->HasEnoughMoney(moneyCost))
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney);
+ return;
+ }
+
+ player->ModifyMoney(-moneyCost);
+
+ npc->SendPlaySpellVisual(179);
+ npc->SendPlaySpellImpact(player->GetGUID(), 362);
+
+ // 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
+ {
+ // check class for class trainers
+ if (player->getClass() != GetTrainerRequirement() && (GetTrainerType() == Type::Class || GetTrainerType() == Type::Pet))
+ return false;
+
+ // check race for mount trainers
+ if (player->getRace() != GetTrainerRequirement() && GetTrainerType() == Type::Mount)
+ return false;
+
+ // check spell for profession trainers
+ if (!player->HasSpell(GetTrainerRequirement()) && GetTrainerRequirement() != 0 && GetTrainerType() == Type::Tradeskill)
+ return false;
+
+ 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);
+ }
+}
diff --git a/src/server/game/Entities/Creature/Trainer.h b/src/server/game/Entities/Creature/Trainer.h
new file mode 100644
index 00000000000..852b3e613be
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 <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 Spell
+ {
+ uint32 SpellId = 0;
+ uint32 MoneyCost = 0;
+ uint32 ReqSkillLine = 0;
+ uint32 ReqSkillRank = 0;
+ std::array<uint32, 3> ReqAbility = { };
+ uint8 ReqLevel = 0;
+
+ bool IsCastable() const;
+ };
+
+ class Trainer
+ {
+ public:
+ Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector<Spell> spells);
+
+ void SendSpells(Creature const* npc, Player const* player, LocaleConstant locale) const;
+ void TeachSpell(Creature const* npc, Player* player, uint32 spellId) const;
+
+ Type GetTrainerType() const { return _type; }
+ uint32 GetTrainerRequirement() const { return _requirement; }
+ bool IsTrainerValidForPlayer(Player const* player) const;
+
+ private:
+ Spell const* GetSpell(uint32 spellId) const;
+ bool CanTeachSpell(Player const* player, Spell const* trainerSpell) const;
+ 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;
+ 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__
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp
index a9a9c78c0bb..3e6a080ac5b 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -1741,19 +1741,19 @@ void Unit::BuildHeartBeatMsg(WorldPacket* data) const
BuildMovementPacket(data);
}
-void WorldObject::SendMessageToSet(WorldPacket const* data, bool self)
+void WorldObject::SendMessageToSet(WorldPacket const* data, bool self) const
{
if (IsInWorld())
SendMessageToSetInRange(data, GetVisibilityRange(), self);
}
-void WorldObject::SendMessageToSetInRange(WorldPacket const* data, float dist, bool /*self*/)
+void WorldObject::SendMessageToSetInRange(WorldPacket const* data, float dist, bool /*self*/) const
{
Trinity::MessageDistDeliverer notifier(this, data, dist);
Cell::VisitWorldObjects(this, notifier, dist);
}
-void WorldObject::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr)
+void WorldObject::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const
{
Trinity::MessageDistDeliverer notifier(this, data, GetVisibilityRange(), false, skipped_rcvr);
Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 76824c2719e..a40765d1f0d 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -344,9 +344,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
virtual void CleanupsBeforeDelete(bool finalCleanup = true); // used in destructor or explicitly before mass creature delete to remove cross-references to already deleted units
- virtual void SendMessageToSet(WorldPacket const* data, bool self);
- virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self);
- virtual void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr);
+ virtual void SendMessageToSet(WorldPacket const* data, bool self) const;
+ virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const;
+ virtual void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const;
virtual uint8 getLevelForTarget(WorldObject const* /*target*/) const { return 1; }
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 7de2017eb4b..3068b969bbd 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -85,6 +85,7 @@
#include "SpellMgr.h"
#include "TicketMgr.h"
#include "TradeData.h"
+#include "Trainer.h"
#include "Transport.h"
#include "UpdateData.h"
#include "UpdateFieldFlags.h"
@@ -3962,74 +3963,6 @@ bool Player::HasActiveSpell(uint32 spell) const
itr->second->active && !itr->second->disabled);
}
-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->ReqAbility[i])
- continue;
-
- if (!HasSpell(trainer_spell->ReqAbility[i]))
- {
- hasSpell = false;
- break;
- }
- }
- // known spell
- if (hasSpell)
- return TRAINER_SPELL_GRAY;
-
- // check skill requirement
- if (trainer_spell->ReqSkillLine && GetBaseSkillValue(trainer_spell->ReqSkillLine) < trainer_spell->ReqSkillRank)
- 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->ReqAbility[i])
- continue;
-
- // check race/class requirement
- if (!IsSpellFitByClassAndRace(trainer_spell->ReqAbility[i]))
- return TRAINER_SPELL_RED;
-
- if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->ReqAbility[i]))
- {
- // check prev.rank requirement
- if (!HasSpell(prevSpell))
- return TRAINER_SPELL_RED;
- }
-
- SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->ReqAbility[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 professions available is disabled
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!trainer_spell->ReqAbility[i])
- continue;
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->ReqAbility[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0))
- return TRAINER_SPELL_GREEN_DISABLED;
- }
-
- return TRAINER_SPELL_GREEN;
-}
-
/**
* Deletes a character from the database
*
@@ -6331,7 +6264,7 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation, bool t
return true;
}
-void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self)
+void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const
{
if (self)
SendDirectMessage(data);
@@ -6340,7 +6273,7 @@ void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool s
Cell::VisitWorldObjects(this, notifier, dist);
}
-void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only)
+void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) const
{
if (self)
SendDirectMessage(data);
@@ -6349,7 +6282,7 @@ void Player::SendMessageToSetInRange(WorldPacket const* data, float dist, bool s
Cell::VisitWorldObjects(this, notifier, dist);
}
-void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr)
+void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const
{
if (skipped_rcvr != this)
SendDirectMessage(data);
@@ -14116,15 +14049,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:
@@ -14143,13 +14076,16 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
canTalk = false;
break;
case GOSSIP_OPTION_TRAINER:
- if (getClass() != creature->GetCreatureTemplate()->trainer_class && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS)
+ {
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
+ if (!trainer || !trainer->IsTrainerValidForPlayer(this))
{
- TC_LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (GUID: %u) requested wrong gossip menu: %u with wrong class: %u at Creature: %s (Entry: %u, Trainer Class: %u)",
- GetName().c_str(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), getClass(), creature->GetName().c_str(), creature->GetEntry(), creature->GetCreatureTemplate()->trainer_class);
+ TC_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;
}
// no break;
+ }
case GOSSIP_OPTION_GOSSIP:
case GOSSIP_OPTION_SPIRITGUIDE:
case GOSSIP_OPTION_INNKEEPER:
@@ -14329,7 +14265,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))
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 3577eea21e4..fc5db7a3726 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -155,14 +155,6 @@ typedef std::unordered_set<SpellModifier*> SpellModContainer;
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,
@@ -1401,7 +1393,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SendRemoveControlBar() const;
bool HasSpell(uint32 spell) const override;
bool HasActiveSpell(uint32 spell) const; // show in spellbook
- TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
bool IsSpellFitByClassAndRace(uint32 spell_id) const;
bool HandlePassiveSpellLearn(SpellInfo const* spellInfo);
@@ -1661,10 +1652,10 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData) override;
void AtExitCombat() override;
- void SendMessageToSet(WorldPacket const* data, bool self) override { SendMessageToSetInRange(data, GetVisibilityRange(), self); }
- void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) override;
- void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only);
- void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) override;
+ void SendMessageToSet(WorldPacket const* data, bool self) const override { SendMessageToSetInRange(data, GetVisibilityRange(), self); }
+ void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const override;
+ void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self, bool own_team_only) const;
+ void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const override;
Corpse* GetCorpse() const;
void SpawnCorpseBones(bool triggerSave = true);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 0acd39162c8..d02bc63073d 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -11785,7 +11785,7 @@ void Unit::SetAuraStack(uint32 spellId, Unit* target, uint32 stack)
aura->SetStackAmount(stack);
}
-void Unit::SendPlaySpellVisual(uint32 id)
+void Unit::SendPlaySpellVisual(uint32 id) const
{
WorldPacket data(SMSG_PLAY_SPELL_VISUAL, 8 + 4);
data << uint64(GetGUID());
@@ -11793,7 +11793,7 @@ void Unit::SendPlaySpellVisual(uint32 id)
SendMessageToSet(&data, true);
}
-void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id)
+void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) const
{
WorldPacket data(SMSG_PLAY_SPELL_IMPACT, 8 + 4);
data << uint64(guid); // target
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index c588ef426f5..0a4445d5fb7 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1081,8 +1081,8 @@ class TC_GAME_API Unit : public WorldObject
Aura* AddAura(uint32 spellId, Unit* target);
Aura* AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target);
void SetAuraStack(uint32 spellId, Unit* target, uint32 stack);
- void SendPlaySpellVisual(uint32 id);
- void SendPlaySpellImpact(ObjectGuid guid, uint32 id);
+ void SendPlaySpellVisual(uint32 id) const;
+ void SendPlaySpellImpact(ObjectGuid guid, uint32 id) const;
void DeMorph();
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 80f823f9beb..ff0bc2938c0 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -367,15 +367,13 @@ void ObjectMgr::LoadCreatureTemplates()
"modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, "
// 21 22 23 24 25 26 27 28 29 30
"scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, "
- // 31 32 33 34 35 36 37
- "dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, "
- // 38 39 40 41 42 43 44 45 46 47 48
- "type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, "
- // 49 50 51 52 53 54 55 56 57 58 59 60 61
- "spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, "
- // 62 63 64 65 66 67 68 69 70
+ // 31 32 33 34 35 36 37 38 39 40 41 42 43
+ "dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, "
+ // 44 45 46 47 48 49 50 51 52 53 54 55 56 57
+ "spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, "
+ // 58 59 60 61 62 63 64 65 66
"ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, "
- // 71 72 73 74 75 76 77 78
+ // 67 68 69 70 71 72 73 74
"ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName "
"FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId");
@@ -439,54 +437,50 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields)
creatureTemplate.unit_flags2 = fields[30].GetUInt32();
creatureTemplate.dynamicflags = fields[31].GetUInt32();
creatureTemplate.family = CreatureFamily(fields[32].GetUInt8());
- creatureTemplate.trainer_type = fields[33].GetUInt8();
- creatureTemplate.trainer_spell = fields[34].GetUInt32();
- creatureTemplate.trainer_class = fields[35].GetUInt8();
- creatureTemplate.trainer_race = fields[36].GetUInt8();
- creatureTemplate.type = fields[37].GetUInt8();
- creatureTemplate.type_flags = fields[38].GetUInt32();
- creatureTemplate.lootid = fields[39].GetUInt32();
- creatureTemplate.pickpocketLootId = fields[40].GetUInt32();
- creatureTemplate.SkinLootId = fields[41].GetUInt32();
+ creatureTemplate.type = fields[33].GetUInt8();
+ creatureTemplate.type_flags = fields[34].GetUInt32();
+ creatureTemplate.lootid = fields[35].GetUInt32();
+ creatureTemplate.pickpocketLootId = fields[36].GetUInt32();
+ creatureTemplate.SkinLootId = fields[37].GetUInt32();
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
- creatureTemplate.resistance[i] = fields[42 + i - 1].GetInt16();
+ creatureTemplate.resistance[i] = fields[38 + i - 1].GetInt16();
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
- creatureTemplate.spells[i] = fields[48 + i].GetUInt32();
-
- creatureTemplate.PetSpellDataId = fields[56].GetUInt32();
- creatureTemplate.VehicleId = fields[57].GetUInt32();
- creatureTemplate.mingold = fields[58].GetUInt32();
- creatureTemplate.maxgold = fields[59].GetUInt32();
- creatureTemplate.AIName = fields[60].GetString();
- creatureTemplate.MovementType = fields[61].GetUInt8();
- if (!fields[62].IsNull())
- creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[62].GetUInt8());
-
- if (!fields[63].IsNull())
- creatureTemplate.Movement.Swim = fields[63].GetBool();
-
- if (!fields[64].IsNull())
- creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[64].GetUInt8());
-
- if (!fields[65].IsNull())
- creatureTemplate.Movement.Rooted = fields[65].GetBool();
-
- creatureTemplate.HoverHeight = fields[66].GetFloat();
- creatureTemplate.ModHealth = fields[67].GetFloat();
- creatureTemplate.ModMana = fields[68].GetFloat();
- creatureTemplate.ModArmor = fields[69].GetFloat();
- creatureTemplate.ModDamage = fields[70].GetFloat();
- creatureTemplate.ModExperience = fields[71].GetFloat();
- creatureTemplate.RacialLeader = fields[72].GetBool();
-
- creatureTemplate.movementId = fields[73].GetUInt32();
- creatureTemplate.RegenHealth = fields[74].GetBool();
- creatureTemplate.MechanicImmuneMask = fields[75].GetUInt32();
- creatureTemplate.SpellSchoolImmuneMask = fields[76].GetUInt32();
- creatureTemplate.flags_extra = fields[77].GetUInt32();
- creatureTemplate.ScriptID = GetScriptId(fields[78].GetString());
+ creatureTemplate.spells[i] = fields[44 + i].GetUInt32();
+
+ creatureTemplate.PetSpellDataId = fields[52].GetUInt32();
+ creatureTemplate.VehicleId = fields[53].GetUInt32();
+ creatureTemplate.mingold = fields[54].GetUInt32();
+ creatureTemplate.maxgold = fields[55].GetUInt32();
+ creatureTemplate.AIName = fields[56].GetString();
+ creatureTemplate.MovementType = fields[57].GetUInt8();
+ if (!fields[58].IsNull())
+ creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[58].GetUInt8());
+
+ if (!fields[59].IsNull())
+ creatureTemplate.Movement.Swim = fields[59].GetBool();
+
+ if (!fields[60].IsNull())
+ creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[60].GetUInt8());
+
+ if (!fields[61].IsNull())
+ creatureTemplate.Movement.Rooted = fields[61].GetBool();
+
+ creatureTemplate.HoverHeight = fields[62].GetFloat();
+ creatureTemplate.ModHealth = fields[63].GetFloat();
+ creatureTemplate.ModMana = fields[64].GetFloat();
+ creatureTemplate.ModArmor = fields[65].GetFloat();
+ creatureTemplate.ModDamage = fields[66].GetFloat();
+ creatureTemplate.ModExperience = fields[67].GetFloat();
+ creatureTemplate.RacialLeader = fields[68].GetBool();
+
+ creatureTemplate.movementId = fields[69].GetUInt32();
+ creatureTemplate.RegenHealth = fields[70].GetBool();
+ creatureTemplate.MechanicImmuneMask = fields[71].GetUInt32();
+ creatureTemplate.SpellSchoolImmuneMask = fields[72].GetUInt32();
+ creatureTemplate.flags_extra = fields[73].GetUInt32();
+ creatureTemplate.ScriptID = GetScriptId(fields[74].GetString());
}
void ObjectMgr::LoadCreatureTemplateAddons()
@@ -687,39 +681,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
cInfo->family, cInfo->DifficultyEntry[diff]);
}
- if (cInfo->trainer_class != difficultyInfo->trainer_class)
- {
- TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_class: %u) has different `trainer_class` in difficulty %u mode (Entry: %u, trainer_class: %u).",
- cInfo->Entry, cInfo->trainer_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_class);
- TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_class`=%u WHERE `entry`=%u;",
- cInfo->trainer_class, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_race != difficultyInfo->trainer_race)
- {
- TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_race: %u) has different `trainer_race` in difficulty %u mode (Entry: %u, trainer_race: %u).",
- cInfo->Entry, cInfo->trainer_race, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_race);
- TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_race`=%u WHERE `entry`=%u;",
- cInfo->trainer_race, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_type != difficultyInfo->trainer_type)
- {
- TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, trainer_type: %u) has different `trainer_type` in difficulty %u mode (Entry: %u, trainer_type: %u).",
- cInfo->Entry, cInfo->trainer_type, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->trainer_type);
- TC_LOG_ERROR("sql.sql", "Possible FIX: UPDATE `creature_template` SET `trainer_type`=%u WHERE `entry`=%u;",
- cInfo->trainer_type, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
- {
- TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has different `trainer_spell` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
if (cInfo->type != difficultyInfo->type)
{
TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, type: %u) has different `type` in difficulty %u mode (Entry: %u, type: %u).",
@@ -912,9 +873,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
if (cInfo->RangeAttackTime == 0)
const_cast<CreatureTemplate*>(cInfo)->RangeAttackTime = BASE_ATTACK_TIME;
- if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE)
- TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong trainer type %u.", cInfo->Entry, cInfo->trainer_type);
-
if (cInfo->speed_walk == 0.0f)
{
TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has wrong value (%f) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
@@ -8702,122 +8660,149 @@ void ObjectMgr::LoadMailLevelRewards()
TC_LOG_INFO("server.loading", ">> Loaded %u level dependent mail rewards in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
}
-void ObjectMgr::AddSpellToTrainer(uint32 ID, uint32 SpellID, uint32 MoneyCost, uint32 ReqSkillLine, uint32 ReqSkillRank, uint32 ReqLevel)
+void ObjectMgr::LoadTrainers()
{
- if (ID >= TRINITY_TRAINER_START_REF)
- return;
+ uint32 oldMSTime = getMSTime();
- CreatureTemplate const* cInfo = GetCreatureTemplate(ID);
- if (!cInfo)
- {
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a non-existing creature template (ID: %u), ignoring", ID);
- return;
- }
+ // For reload case
+ _trainers.clear();
- if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
+ std::unordered_map<int32, std::vector<Trainer::Spell>> spellsByTrainer;
+ if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell"))
{
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains entries for a creature template (ID: %u) without trainer flag, ignoring", ID);
- return;
- }
+ do
+ {
+ Field* fields = trainerSpellsResult->Fetch();
- SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(SpellID);
- if (!spellinfo)
- {
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing SpellID (%u), ignoring", ID, SpellID);
- return;
- }
+ Trainer::Spell spell;
+ uint32 trainerId = fields[0].GetUInt32();
+ spell.SpellId = fields[1].GetUInt32();
+ spell.MoneyCost = fields[2].GetUInt32();
+ spell.ReqSkillLine = fields[3].GetUInt32();
+ spell.ReqSkillRank = fields[4].GetUInt32();
+ spell.ReqAbility[0] = fields[5].GetUInt32();
+ spell.ReqAbility[1] = fields[6].GetUInt32();
+ spell.ReqAbility[2] = fields[7].GetUInt32();
+ spell.ReqLevel = fields[8].GetUInt8();
- if (!SpellMgr::IsSpellValid(spellinfo))
- {
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a broken SpellID (%u), ignoring", ID, SpellID);
- return;
- }
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
+ if (!spellInfo)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: %u) for TrainerId %u, ignoring", spell.SpellId, trainerId);
+ continue;
+ }
- if (GetTalentSpellCost(SpellID))
- {
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` contains an ID (%u) for a non-existing SpellID (%u) which is a talent, ignoring", ID, SpellID);
- return;
- }
+ if (GetTalentSpellCost(spell.SpellId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: %u) which is a talent, for TrainerId %u, ignoring", spell.SpellId, trainerId);
+ continue;
+ }
- TrainerSpellData& data = _cacheTrainerSpellStore[ID];
+ if (spell.ReqSkillLine && !sSkillLineStore.LookupEntry(spell.ReqSkillLine))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing skill (ReqSkillLine: %u) for TrainerId %u and SpellId %u, ignoring",
+ spell.ReqSkillLine, spell.SpellId, trainerId);
+ continue;
+ }
- TrainerSpell& trainerSpell = data.spellList[SpellID];
- trainerSpell.SpellID = SpellID;
- trainerSpell.MoneyCost = MoneyCost;
- trainerSpell.ReqSkillLine = ReqSkillLine;
- trainerSpell.ReqSkillRank = ReqSkillRank;
- trainerSpell.ReqLevel = ReqLevel;
+ bool allReqValid = true;
+ for (std::size_t i = 0; i < spell.ReqAbility.size(); ++i)
+ {
+ uint32 requiredSpell = spell.ReqAbility[i];
+ if (requiredSpell && !sSpellMgr->GetSpellInfo(requiredSpell))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (ReqAbility" SZFMTD ": %u) for TrainerId %u and SpellId %u, ignoring",
+ i + 1, requiredSpell, trainerId, spell.SpellId);
+ allReqValid = false;
+ }
+ }
+
+ if (!allReqValid)
+ continue;
- if (!trainerSpell.ReqLevel)
- trainerSpell.ReqLevel = spellinfo->SpellLevel;
+ spellsByTrainer[trainerId].push_back(spell);
+ } while (trainerSpellsResult->NextRow());
+ }
- // calculate learned spell for profession case when stored cast-spell
- trainerSpell.ReqAbility[0] = SpellID;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Requirement, Greeting FROM trainer"))
{
- if (spellinfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL)
- continue;
- if (trainerSpell.ReqAbility[0] == SpellID)
- trainerSpell.ReqAbility[0] = 0;
- // player must be able to cast spell on himself
- if (spellinfo->Effects[i].TargetA.GetTarget() != 0 && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY
- && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_CASTER)
+ do
{
- TC_LOG_ERROR("sql.sql", "Table `npc_trainer` has spell %u for trainer entry %u with learn effect which has incorrect target type, ignoring learn effect!", SpellID, ID);
- continue;
- }
+ Field* fields = trainersResult->Fetch();
+
+ uint32 trainerId = fields[0].GetUInt32();
+ Trainer::Type trainerType = Trainer::Type(fields[1].GetUInt8());
+ uint32 requirement = fields[2].GetUInt32();
+ std::string greeting = fields[3].GetString();
+ std::vector<Trainer::Spell> spells;
+ auto spellsItr = spellsByTrainer.find(trainerId);
+ if (spellsItr != spellsByTrainer.end())
+ {
+ spells = std::move(spellsItr->second);
+ spellsByTrainer.erase(spellsItr);
+ }
- trainerSpell.ReqAbility[i] = spellinfo->Effects[i].TriggerSpell;
+ _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells)));
+ } while (trainersResult->NextRow());
+ }
- if (trainerSpell.ReqAbility[i])
+ for (auto const& unusedSpells : spellsByTrainer)
+ {
+ for (Trainer::Spell const& unusedSpell : unusedSpells.second)
{
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsProfession())
- data.trainerType = 2;
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: %u) for SpellId %u, ignoring", unusedSpells.first, unusedSpell.SpellId);
}
}
- return;
-}
-
-void ObjectMgr::LoadTrainerSpell()
-{
- uint32 oldMSTime = getMSTime();
+ if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale"))
+ {
+ do
+ {
+ Field* fields = trainerLocalesResult->Fetch();
+ uint32 trainerId = fields[0].GetUInt32();
+ std::string localeName = fields[1].GetString();
- // For reload case
- _cacheTrainerSpellStore.clear();
+ LocaleConstant locale = GetLocaleByName(localeName);
+ if (locale == LOCALE_enUS)
+ continue;
- QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.ReqLevel FROM npc_trainer AS a "
- "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) "
- "UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
+ if (Trainer::Trainer* trainer = Trinity::Containers::MapGetValuePtr(_trainers, trainerId))
+ trainer->AddGreetingLocale(locale, fields[2].GetString());
+ else
+ TC_LOG_ERROR("sql.sql", "Table `trainer_locale` references non-existing trainer (TrainerId: %u) for locale %s, ignoring",
+ trainerId, localeName.c_str());
+ } while (trainerLocalesResult->NextRow());
+ }
- if (!result)
- {
- TC_LOG_ERROR("sql.sql", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!");
+ TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " Trainers in %u ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime));
+}
- return;
- }
+void ObjectMgr::LoadCreatureDefaultTrainers()
+{
+ uint32 oldMSTime = getMSTime();
- uint32 count = 0;
+ _creatureDefaultTrainers.clear();
- do
+ if (QueryResult result = WorldDatabase.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer"))
{
- Field* fields = result->Fetch();
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 creatureId = fields[0].GetUInt32();
+ uint32 trainerId = fields[1].GetUInt32();
- uint32 ID = fields[0].GetUInt32();
- uint32 SpellID = fields[1].GetUInt32();
- uint32 MoneyCost = fields[2].GetUInt32();
- uint32 ReqSkillLine = fields[3].GetUInt16();
- uint32 ReqSkillRank = fields[4].GetUInt16();
- uint32 ReqLevel = fields[5].GetUInt8();
+ if (!GetCreatureTemplate(creatureId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `creature_default_trainer` references non-existing creature template (CreatureId: %u), ignoring", creatureId);
+ continue;
+ }
- AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel);
+ _creatureDefaultTrainers[creatureId] = trainerId;
- ++count;
+ } while (result->NextRow());
}
- while (result->NextRow());
- TC_LOG_INFO("server.loading", ">> Loaded %d Trainers in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
+ TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " default trainers in %u ms", _creatureDefaultTrainers.size(), GetMSTimeDiffToNow(oldMSTime));
}
uint32 ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32>* skip_vendors)
@@ -9022,6 +9007,15 @@ void ObjectMgr::LoadGossipMenuItems()
TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option entries in %u ms", uint32(_gossipMenuItemsStore.size()), GetMSTimeDiffToNow(oldMSTime));
}
+Trainer::Trainer const* ObjectMgr::GetTrainer(uint32 creatureId) const
+{
+ auto itr = _creatureDefaultTrainers.find(creatureId);
+ if (itr != _creatureDefaultTrainers.end())
+ return Trinity::Containers::MapGetValuePtr(_trainers, itr->second);
+
+ return nullptr;
+}
+
void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedCost, bool persist /*= true*/)
{
VendorItemData& vList = _cacheVendorItemStore[entry];
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
index 7c5fa315d3d..8568e72a601 100644
--- a/src/server/game/Globals/ObjectMgr.h
+++ b/src/server/game/Globals/ObjectMgr.h
@@ -33,6 +33,7 @@
#include "Position.h"
#include "QuestDef.h"
#include "SharedDefines.h"
+#include "Trainer.h"
#include "VehicleDefines.h"
#include <map>
#include <unordered_map>
@@ -828,7 +829,6 @@ typedef std::pair<GraveyardContainer::const_iterator, GraveyardContainer::const_
typedef std::pair<GraveyardContainer::iterator, GraveyardContainer::iterator> GraveyardMapBoundsNonConst;
typedef std::unordered_map<uint32, VendorItemData> CacheVendorItemContainer;
-typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer;
enum SkillRangeType
{
@@ -1215,8 +1215,8 @@ class TC_GAME_API ObjectMgr
void LoadGossipMenuItems();
void LoadVendors();
- void LoadTrainerSpell();
- void AddSpellToTrainer(uint32 ID, uint32 SpellID, uint32 MoneyCost, uint32 ReqSkillLine, uint32 ReqSkillRank, uint32 ReqLevel);
+ void LoadTrainers();
+ void LoadCreatureDefaultTrainers();
void InitializeQueriesData(QueryDataGroup mask);
@@ -1456,14 +1456,7 @@ class TC_GAME_API ObjectMgr
bool AddGameTele(GameTele& data);
bool DeleteGameTele(std::string const& name);
- TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const
- {
- CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry);
- if (iter == _cacheTrainerSpellStore.end())
- return nullptr;
-
- return &iter->second;
- }
+ Trainer::Trainer const* GetTrainer(uint32 creatureId) const;
VendorItemData const* GetNpcVendorItemList(uint32 entry) const
{
@@ -1685,7 +1678,8 @@ class TC_GAME_API ObjectMgr
TrinityStringContainer _trinityStringStore;
CacheVendorItemContainer _cacheVendorItemStore;
- CacheTrainerSpellContainer _cacheTrainerSpellStore;
+ std::unordered_map<uint32, Trainer::Trainer> _trainers;
+ std::unordered_map<uint32, uint32> _creatureDefaultTrainers;
std::set<uint32> _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
std::set<uint32> _hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h
index 5297d21b104..5e606e67ef1 100644
--- a/src/server/game/Grids/Notifiers/GridNotifiers.h
+++ b/src/server/game/Grids/Notifiers/GridNotifiers.h
@@ -116,19 +116,19 @@ namespace Trinity
struct TC_GAME_API MessageDistDeliverer
{
- WorldObject* i_source;
+ WorldObject const* i_source;
WorldPacket const* i_message;
uint32 i_phaseMask;
float i_distSq;
uint32 team;
Player const* skipped_receiver;
- MessageDistDeliverer(WorldObject* src, WorldPacket const* msg, float dist, bool own_team_only = false, Player const* skipped = nullptr)
+ MessageDistDeliverer(WorldObject const* src, WorldPacket const* msg, float dist, bool own_team_only = false, Player const* skipped = nullptr)
: i_source(src), i_message(msg), i_phaseMask(src->GetPhaseMask()), i_distSq(dist * dist)
, team(0)
, skipped_receiver(skipped)
{
if (own_team_only)
- if (Player* player = src->ToPlayer())
+ if (Player const* player = src->ToPlayer())
team = player->GetTeam();
}
diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp
index 5b50075d2fd..dbbf3b6fe03 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -29,6 +29,7 @@
#include "Language.h"
#include "Log.h"
#include "Map.h"
+#include "NPCPackets.h"
#include "Opcodes.h"
#include "ObjectMgr.h"
#include "Pet.h"
@@ -38,6 +39,7 @@
#include "ScriptMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
+#include "Trainer.h"
#include "World.h"
#include "WorldPacket.h"
@@ -114,135 +116,45 @@ void WorldSession::SendShowMailBox(ObjectGuid guid)
SendPacket(&data);
}
-void WorldSession::HandleTrainerListOpcode(WorldPacket& recvData)
+void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet)
{
- ObjectGuid guid;
-
- recvData >> guid;
- SendTrainerList(guid);
-}
-
-void WorldSession::SendTrainerList(ObjectGuid guid)
-{
- std::string str = GetTrinityString(LANG_NPC_TAINER_HELLO);
- SendTrainerList(guid, str);
-}
-
-void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle)
-{
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
- if (!unit)
+ Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TRAINER);
+ if (!npc)
{
- TC_LOG_DEBUG("network", "WORLD: SendTrainerList - %s not found or you can not interact with him.", guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WorldSession: SendTrainerList - %s not found or you can not interact with him.", packet.Unit.ToString().c_str());
return;
}
+ if (sObjectMgr->GetTrainer(npc->GetEntry()))
+ SendTrainerList(npc);
+ else
+ TC_LOG_DEBUG("network", "WorldSession::SendTrainerList - Creature id %u has no trainer data.", npc->GetEntry());
+}
+
+void WorldSession::SendTrainerList(Creature* npc)
+{
// remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- TrainerSpellData const* trainer_spells = unit->GetTrainerSpells();
- if (!trainer_spells)
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
+ if (!trainer)
{
- TC_LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for %s", guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer spells not found for %s", npc->GetGUID().ToString().c_str());
return;
}
- WorldPacket data(SMSG_TRAINER_LIST, 8+4+4+trainer_spells->spellList.size()*38 + strTitle.size()+1);
- data << guid;
- data << uint32(trainer_spells->trainerType);
-
- size_t count_pos = data.wpos();
- data << uint32(trainer_spells->spellList.size());
-
- // reputation discount
- float fDiscountMod = _player->GetReputationPriceDiscount(unit);
- bool can_learn_primary_prof = GetPlayer()->GetFreePrimaryProfessionPoints() > 0;
-
- uint32 count = 0;
- for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr)
- {
- TrainerSpell const* tSpell = &itr->second;
-
- bool valid = true;
- bool primary_prof_first_rank = false;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!tSpell->ReqAbility[i])
- continue;
- if (!_player->IsSpellFitByClassAndRace(tSpell->ReqAbility[i]))
- {
- valid = false;
- break;
- }
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(tSpell->ReqAbility[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank())
- primary_prof_first_rank = true;
- }
- if (!valid)
- continue;
-
- TrainerSpellState state = _player->GetTrainerSpellState(tSpell);
-
- data << uint32(tSpell->SpellID); // learned spell (or cast-spell in profession case)
- data << uint8(state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state);
- data << uint32(floor(tSpell->MoneyCost * fDiscountMod));
-
- data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0);
- // primary prof. learn confirmation dialog
- data << uint32(primary_prof_first_rank ? 1 : 0); // must be equal prev. field to have learn button in enabled state
- data << uint8(tSpell->ReqLevel);
- data << uint32(tSpell->ReqSkillLine);
- data << uint32(tSpell->ReqSkillRank);
- //prev + req or req + 0
- uint8 maxReq = 0;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!tSpell->ReqAbility[i])
- continue;
- if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->ReqAbility[i]))
- {
- data << uint32(prevSpellId);
- ++maxReq;
- }
- if (maxReq == 3)
- break;
- SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->ReqAbility[i]);
- for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequired.first; itr2 != spellsRequired.second && maxReq < 3; ++itr2)
- {
- data << uint32(itr2->second);
- ++maxReq;
- }
- if (maxReq == 3)
- break;
- }
- while (maxReq < 3)
- {
- data << uint32(0);
- ++maxReq;
- }
-
- ++count;
- }
-
- data << strTitle;
-
- data.put<uint32>(count_pos, count);
- SendPacket(&data);
+ trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex());
}
-void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData)
+void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet)
{
- ObjectGuid guid;
- uint32 spellId = 0;
+ TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %u", packet.TrainerGUID.ToString().c_str(), packet.SpellID);
- recvData >> guid >> spellId;
- TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %u", guid.ToString().c_str(), spellId);
-
- Creature* trainer = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
- if (!trainer)
+ Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, UNIT_NPC_FLAG_TRAINER);
+ if (!npc)
{
- TC_LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can not interact with him.", guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - %s not found or you can not interact with him.", packet.TrainerGUID.ToString().c_str());
return;
}
@@ -250,54 +162,11 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- // check race for mount trainers
- if (trainer->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_MOUNTS)
- {
- if (uint32 trainerRace = trainer->GetCreatureTemplate()->trainer_race)
- if (_player->getRace() != trainerRace)
- return;
- }
-
- // check class for class trainers
- if (_player->getClass() != trainer->GetCreatureTemplate()->trainer_class && trainer->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS)
- return;
-
- // check present spell in trainer spell list
- TrainerSpellData const* trainer_spells = trainer->GetTrainerSpells();
- if (!trainer_spells)
- return;
-
- // not found, cheat?
- TrainerSpell const* trainer_spell = trainer_spells->Find(spellId);
- if (!trainer_spell)
- return;
-
- // can't be learn, cheat? Or double learn with lags...
- if (_player->GetTrainerSpellState(trainer_spell) != TRAINER_SPELL_GREEN)
- return;
-
- // apply reputation discount
- uint32 nSpellCost = uint32(floor(trainer_spell->MoneyCost * _player->GetReputationPriceDiscount(trainer)));
-
- // check money requirement
- if (!_player->HasEnoughMoney(nSpellCost))
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
+ if (!trainer)
return;
- _player->ModifyMoney(-int32(nSpellCost));
-
- trainer->SendPlaySpellVisual(179); // 53 SpellCastDirected
- trainer->SendPlaySpellImpact(_player->GetGUID(), 362); // 113 EmoteSalute
-
- // learn explicitly or cast explicitly
- if (trainer_spell->IsCastable())
- _player->CastSpell(_player, trainer_spell->SpellID, true);
- else
- _player->LearnSpell(spellId, false);
-
- WorldPacket data(SMSG_TRAINER_BUY_SUCCEEDED, 12);
- data << uint64(guid);
- data << uint32(spellId); // should be same as in packet from client
- SendPacket(&data);
+ trainer->TeachSpell(npc, _player, packet.SpellID);
}
void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp
index 0e9d45e1aca..b254b891a4a 100644
--- a/src/server/game/Handlers/SkillHandler.cpp
+++ b/src/server/game/Handlers/SkillHandler.cpp
@@ -71,7 +71,7 @@ void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData)
return;
}
- if (!unit->isCanTrainingAndResetTalentsOf(_player))
+ if (!unit->CanResetTalents(_player))
return;
// remove fake death
diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h
index 63cd9c0ca4c..17bcce8f09a 100644
--- a/src/server/game/Server/Packets/AllPackets.h
+++ b/src/server/game/Server/Packets/AllPackets.h
@@ -18,6 +18,7 @@
#ifndef AllPackets_h__
#define AllPackets_h__
+#include "NPCPackets.h"
#include "QueryPackets.h"
#include "QuestPackets.h"
#include "SpellPackets.h"
diff --git a/src/server/game/Server/Packets/NPCPackets.cpp b/src/server/game/Server/Packets/NPCPackets.cpp
new file mode 100644
index 00000000000..bef58fabd02
--- /dev/null
+++ b/src/server/game/Server/Packets/NPCPackets.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "NPCPackets.h"
+
+void WorldPackets::NPC::Hello::Read()
+{
+ _worldPacket >> Unit;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerList::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(TrainerType);
+
+ _worldPacket << int32(Spells.size());
+ for (TrainerListSpell const& spell : Spells)
+ {
+ _worldPacket << int32(spell.SpellID);
+ _worldPacket << uint8(spell.Usable);
+ _worldPacket << int32(spell.MoneyCost);
+ _worldPacket << int32(spell.ProfessionDialog);
+ _worldPacket << int32(spell.ProfessionButton);
+ _worldPacket << uint8(spell.ReqLevel);
+ _worldPacket << int32(spell.ReqSkillLine);
+ _worldPacket << int32(spell.ReqSkillRank);
+ _worldPacket.append(spell.ReqAbility.data(), spell.ReqAbility.size());
+ }
+
+ _worldPacket << Greeting;
+
+ return &_worldPacket;
+}
+
+void WorldPackets::NPC::TrainerBuySpell::Read()
+{
+ _worldPacket >> TrainerGUID;
+ _worldPacket >> SpellID;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerBuyFailed::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(SpellID);
+ _worldPacket << int32(TrainerFailedReason);
+
+ return &_worldPacket;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerBuySucceeded::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(SpellID);
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h
new file mode 100644
index 00000000000..cdbf7072a8a
--- /dev/null
+++ b/src/server/game/Server/Packets/NPCPackets.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NPCPackets_h__
+#define NPCPackets_h__
+
+#include "Packet.h"
+#include "ObjectGuid.h"
+#include <array>
+
+namespace WorldPackets
+{
+ namespace NPC
+ {
+ // CMSG_BANKER_ACTIVATE
+ // CMSG_BATTLEMASTER_HELLO
+ // CMSG_BINDER_ACTIVATE
+ // CMSG_GOSSIP_HELLO
+ // CMSG_LIST_INVENTORY
+ // CMSG_TRAINER_LIST
+ class Hello final : public ClientPacket
+ {
+ public:
+ Hello(WorldPacket&& packet) : ClientPacket(std::move(packet)) { }
+
+ void Read() override;
+
+ ObjectGuid Unit;
+ };
+
+ struct TrainerListSpell
+ {
+ int32 SpellID = 0;
+ uint8 Usable = 0;
+ int32 MoneyCost = 0;
+ int32 ProfessionDialog = 0;
+ int32 ProfessionButton = 0;
+ uint8 ReqLevel = 0;
+ int32 ReqSkillLine = 0;
+ int32 ReqSkillRank = 0;
+ std::array<int32, 3> ReqAbility = { };
+ };
+
+ class TrainerList final : public ServerPacket
+ {
+ public:
+ TrainerList() : ServerPacket(SMSG_TRAINER_LIST) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 TrainerType = 0;
+ std::vector<TrainerListSpell> Spells;
+ std::string Greeting;
+ };
+
+ class TrainerBuySpell final : public ClientPacket
+ {
+ public:
+ TrainerBuySpell(WorldPacket&& packet) : ClientPacket(CMSG_TRAINER_BUY_SPELL, std::move(packet)) { }
+
+ void Read() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ };
+
+ class TrainerBuyFailed final : public ServerPacket
+ {
+ public:
+ TrainerBuyFailed() : ServerPacket(SMSG_TRAINER_BUY_FAILED, 8 + 4 + 4) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ int32 TrainerFailedReason = 0;
+ };
+
+ class TrainerBuySucceeded final : public ServerPacket
+ {
+ public:
+ TrainerBuySucceeded() : ServerPacket(SMSG_TRAINER_BUY_SUCCEEDED, 8 + 4) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ };
+ }
+}
+
+#endif // NPCPackets_h__
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 540bc239128..2891b486201 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -74,6 +74,11 @@ class RBACData;
namespace WorldPackets
{
+ namespace NPC
+ {
+ class Hello;
+ class TrainerBuySpell;
+ }
namespace Query
{
class QueryCreature;
@@ -348,8 +353,7 @@ class TC_GAME_API WorldSession
void SendNameQueryOpcode(ObjectGuid guid);
- void SendTrainerList(ObjectGuid guid);
- void SendTrainerList(ObjectGuid guid, std::string const& strTitle);
+ void SendTrainerList(Creature* npc);
void SendListInventory(ObjectGuid guid);
void SendShowBank(ObjectGuid guid);
bool CanOpenMailBox(ObjectGuid guid);
@@ -652,8 +656,8 @@ class TC_GAME_API WorldSession
void HandleTabardVendorActivateOpcode(WorldPacket& recvPacket);
void HandleBankerActivateOpcode(WorldPacket& recvPacket);
void HandleBuyBankSlotOpcode(WorldPacket& recvPacket);
- void HandleTrainerListOpcode(WorldPacket& recvPacket);
- void HandleTrainerBuySpellOpcode(WorldPacket& recvPacket);
+ void HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet);
+ void HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet);
void HandlePetitionShowListOpcode(WorldPacket& recvPacket);
void HandleGossipHelloOpcode(WorldPacket& recvPacket);
void HandleGossipSelectOptionOpcode(WorldPacket& recvPacket);
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index a5a9f4d33e4..9fe210e493b 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -297,9 +297,9 @@ uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) con
return spell_id;
}
-SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
+Trinity::IteratorPair<SpellRequiredMap::const_iterator> SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
{
- return mSpellReq.equal_range(spell_id);
+ return Trinity::Containers::MapEqualRange(mSpellReq, spell_id);
}
SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const
diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h
index 5dddc5f13e8..595e186b6cd 100644
--- a/src/server/game/Spells/SpellMgr.h
+++ b/src/server/game/Spells/SpellMgr.h
@@ -23,6 +23,7 @@
#include "Define.h"
#include "Duration.h"
+#include "IteratorPair.h"
#include "SharedDefines.h"
#include "Util.h"
@@ -607,7 +608,7 @@ class TC_GAME_API SpellMgr
uint32 GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict = false) const;
// Spell Required table
- SpellRequiredMapBounds GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
+ Trinity::IteratorPair<SpellRequiredMap::const_iterator> GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
SpellsRequiringSpellMapBounds GetSpellsRequiringSpellBounds(uint32 spell_id) const;
bool IsSpellRequiringSpell(uint32 spellid, uint32 req_spellid) const;
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 13c3b7b161c..4c3498c98b0 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1927,18 +1927,21 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading GameTeleports...");
sObjectMgr->LoadGameTele();
+ TC_LOG_INFO("server.loading", "Loading Trainers..."); // must be after LoadCreatureTemplates
+ sObjectMgr->LoadTrainers();
+
+ TC_LOG_INFO("server.loading", "Loading Creature default trainers...");
+ sObjectMgr->LoadCreatureDefaultTrainers();
+
TC_LOG_INFO("server.loading", "Loading Gossip menu...");
sObjectMgr->LoadGossipMenu();
TC_LOG_INFO("server.loading", "Loading Gossip menu options...");
- sObjectMgr->LoadGossipMenuItems();
+ sObjectMgr->LoadGossipMenuItems(); // must be after LoadTrainers
TC_LOG_INFO("server.loading", "Loading Vendors...");
sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
- TC_LOG_INFO("server.loading", "Loading Trainers...");
- sObjectMgr->LoadTrainerSpell(); // must be after load CreatureTemplate
-
TC_LOG_INFO("server.loading", "Loading Waypoints...");
sWaypointMgr->Load();
diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp
index eae194b1044..e5732634b56 100644
--- a/src/server/scripts/Commands/cs_reload.cpp
+++ b/src/server/scripts/Commands/cs_reload.cpp
@@ -124,7 +124,6 @@ public:
{ "mail_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_MAIL_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesMailCommand, "" },
{ "milling_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_MILLING_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesMillingCommand, "" },
{ "npc_spellclick_spells", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_SPELLCLICK_SPELLS, true, &HandleReloadSpellClickSpellsCommand, "" },
- { "npc_trainer", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_TRAINER, true, &HandleReloadNpcTrainerCommand, "" },
{ "npc_vendor", rbac::RBAC_PERM_COMMAND_RELOAD_NPC_VENDOR, true, &HandleReloadNpcVendorCommand, "" },
{ "page_text", rbac::RBAC_PERM_COMMAND_RELOAD_PAGE_TEXT, true, &HandleReloadPageTextsCommand, "" },
{ "pickpocketing_loot_template", rbac::RBAC_PERM_COMMAND_RELOAD_PICKPOCKETING_LOOT_TEMPLATE, true, &HandleReloadLootTemplatesPickpocketingCommand, "" },
@@ -157,6 +156,7 @@ public:
{ "spell_target_position", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, true, &HandleReloadSpellTargetPositionCommand, "" },
{ "spell_threats", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS, true, &HandleReloadSpellThreatsCommand, "" },
{ "spell_group_stack_rules", rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES, true, &HandleReloadSpellGroupStackRulesCommand, "" },
+ { "trainer", rbac::RBAC_PERM_COMMAND_RELOAD_TRAINER, true, &HandleReloadTrainerCommand, "" },
{ "trinity_string", rbac::RBAC_PERM_COMMAND_RELOAD_TRINITY_STRING, true, &HandleReloadTrinityStringCommand, "" },
{ "warden_action", rbac::RBAC_PERM_COMMAND_RELOAD_WARDEN_ACTION, true, &HandleReloadWardenactionCommand, "" },
{ "waypoint_scripts", rbac::RBAC_PERM_COMMAND_RELOAD_WAYPOINT_SCRIPTS, true, &HandleReloadWpScriptsCommand, "" },
@@ -238,7 +238,7 @@ public:
static bool HandleReloadAllNpcCommand(ChatHandler* handler, char const* args)
{
if (*args != 'a') // will be reloaded from all_gossips
- HandleReloadNpcTrainerCommand(handler, "a");
+ HandleReloadTrainerCommand(handler, "a");
HandleReloadNpcVendorCommand(handler, "a");
HandleReloadPointsOfInterestCommand(handler, "a");
HandleReloadSpellClickSpellsCommand(handler, "a");
@@ -711,11 +711,15 @@ public:
return true;
}
- static bool HandleReloadNpcTrainerCommand(ChatHandler* handler, char const* /*args*/)
+ static bool HandleReloadTrainerCommand(ChatHandler* handler, char const* /*args*/)
{
- TC_LOG_INFO("misc", "Re-Loading `npc_trainer` Table!");
- sObjectMgr->LoadTrainerSpell();
- handler->SendGlobalGMSysMessage("DB table `npc_trainer` reloaded.");
+ TC_LOG_INFO("misc", "Re-Loading `trainer` Table!");
+ sObjectMgr->LoadTrainers();
+ sObjectMgr->LoadCreatureDefaultTrainers();
+ handler->SendGlobalGMSysMessage("DB table `trainer` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_locale` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_spell` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `creature_default_trainer` reloaded.");
return true;
}
diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp
index e0b27a40642..1ab3815c9c0 100644
--- a/src/server/scripts/Northrend/zone_storm_peaks.cpp
+++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp
@@ -157,7 +157,7 @@ public:
switch (action)
{
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(me->GetGUID());
+ player->GetSession()->SendTrainerList(me);
break;
case GOSSIP_ACTION_TRADE:
player->GetSession()->SendListInventory(me->GetGUID());
diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp
index a31fc1ee1f6..9601ae2d583 100644
--- a/src/server/scripts/World/npc_professions.cpp
+++ b/src/server/scripts/World/npc_professions.cpp
@@ -495,7 +495,7 @@ public:
player->GetSession()->SendListInventory(me->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(me->GetGUID());
+ player->GetSession()->SendTrainerList(me);
break;
//Learn Armor/Weapon
case GOSSIP_ACTION_INFO_DEF + 1:
@@ -899,7 +899,7 @@ public:
player->GetSession()->SendListInventory(me->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(me->GetGUID());
+ player->GetSession()->SendTrainerList(me);
break;
//Unlearn Leather
case GOSSIP_ACTION_INFO_DEF + 1:
@@ -1041,7 +1041,7 @@ public:
player->GetSession()->SendListInventory(me->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(me->GetGUID());
+ player->GetSession()->SendTrainerList(me);
break;
//Learn Tailor
case GOSSIP_ACTION_INFO_DEF + 1:
diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h
index b27f2f108d9..5a7be809ea8 100644
--- a/src/server/shared/SharedDefines.h
+++ b/src/server/shared/SharedDefines.h
@@ -2621,16 +2621,6 @@ enum LockType
LOCKTYPE_OPEN_FROM_VEHICLE = 21
};
-enum TrainerType // this is important type for npcs!
-{
- TRAINER_TYPE_CLASS = 0,
- TRAINER_TYPE_MOUNTS = 1, // on blizz it's 2
- TRAINER_TYPE_TRADESKILLS = 2,
- TRAINER_TYPE_PETS = 3
-};
-
-#define MAX_TRAINER_TYPE 4
-
// CreatureType.dbc
enum CreatureType
{