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.cpp17
-rw-r--r--src/server/game/Entities/Creature/Creature.h4
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h37
-rw-r--r--src/server/game/Entities/Creature/GossipDef.cpp14
-rw-r--r--src/server/game/Entities/Creature/GossipDef.h7
-rw-r--r--src/server/game/Entities/Creature/Trainer.cpp167
-rw-r--r--src/server/game/Entities/Creature/Trainer.h90
-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.cpp121
-rw-r--r--src/server/game/Entities/Player/Player.h22
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp2
-rw-r--r--src/server/game/Entities/Unit/Unit.h2
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp324
-rw-r--r--src/server/game/Globals/ObjectMgr.h17
-rw-r--r--src/server/game/Grids/Notifiers/GridNotifiers.h6
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp158
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp6
-rw-r--r--src/server/game/Miscellaneous/SharedDefines.h7
-rw-r--r--src/server/game/Server/Packets/NPCPackets.h4
-rw-r--r--src/server/game/Server/WorldSession.h4
-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.cpp10
-rw-r--r--src/server/scripts/Commands/cs_reload.cpp14
27 files changed, 515 insertions, 541 deletions
diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp
index ef469b4e1e7..8f0bf1e384d 100644
--- a/src/server/database/Database/Implementation/WorldDatabase.cpp
+++ b/src/server/database/Database/Implementation/WorldDatabase.cpp
@@ -77,7 +77,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, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template 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, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template 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_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_", CONNECTION_SYNCH);
diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h
index 3a10429f462..a42d20c3cfb 100644
--- a/src/server/game/Accounts/RBAC.h
+++ b/src/server/game/Accounts/RBAC.h
@@ -577,7 +577,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 1bc61659e83..80f775e410a 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -52,15 +52,6 @@
#include "WorldPacket.h"
#include <G3D/g3dmath.h>
-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->GetFlags2() & ITEM_FLAG2_DONT_IGNORE_BUY_PRICE || !ExtendedCost;
@@ -996,10 +987,9 @@ 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() >= 15
- && GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS
&& player->getClass() == GetCreatureTemplate()->trainer_class;
}
@@ -2556,11 +2546,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 c50853724b0..320cb16ad12 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -103,7 +103,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;
bool IsImmunedToSpell(SpellInfo const* spellInfo) const override; // override Unit::IsImmunedToSpell
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; // override Unit::IsImmunedToSpellEffect
@@ -152,8 +152,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 e7fea1304a1..4b62e3ba138 100644
--- a/src/server/game/Entities/Creature/CreatureData.h
+++ b/src/server/game/Entities/Creature/CreatureData.h
@@ -328,9 +328,7 @@ struct TC_GAME_API CreatureTemplate
uint32 unit_flags3; // enum UnitFlags3 mask values
uint32 dynamicflags;
CreatureFamily family; // enum CreatureFamily values (optional)
- uint32 trainer_type;
uint32 trainer_class;
- uint32 trainer_race;
uint32 type; // enum CreatureType values
uint32 type_flags; // enum CreatureTypeFlags mask values
uint32 type_flags2; // unknown enum, only set for 4 creatures (with value 1)
@@ -595,39 +593,4 @@ struct VendorItemData
}
};
-#define MAX_TRAINERSPELL_ABILITY_REQS 3
-
-struct TrainerSpell
-{
- TrainerSpell() : SpellID(0), MoneyCost(0), ReqSkillLine(0), ReqSkillRank(0), ReqLevel(0), Index(0)
- {
- for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i)
- ReqAbility[i] = 0;
- }
-
- uint32 SpellID;
- uint32 MoneyCost;
- uint32 ReqSkillLine;
- uint32 ReqSkillRank;
- uint32 ReqLevel;
- uint32 ReqAbility[MAX_TRAINERSPELL_ABILITY_REQS];
- uint32 Index;
-
- // 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/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp
index d19e9c19a0e..8848fb5aa44 100644
--- a/src/server/game/Entities/Creature/GossipDef.cpp
+++ b/src/server/game/Entities/Creature/GossipDef.cpp
@@ -135,12 +135,13 @@ void GossipMenu::AddMenuItem(uint32 menuId, uint32 menuItemId, uint32 sender, ui
}
}
-void GossipMenu::AddGossipMenuItemData(uint32 menuItemId, uint32 gossipActionMenuId, uint32 gossipActionPoi)
+void GossipMenu::AddGossipMenuItemData(uint32 optionIndex, uint32 gossipActionMenuId, uint32 gossipActionPoi, uint32 trainerId)
{
- GossipMenuItemData& itemData = _menuItemData[menuItemId];
+ GossipMenuItemData& itemData = _menuItemData[optionIndex];
itemData.GossipActionMenuId = gossipActionMenuId;
itemData.GossipActionPoi = gossipActionPoi;
+ itemData.TrainerId = trainerId;
}
uint32 GossipMenu::GetMenuItemSender(uint32 menuItemId) const
@@ -170,6 +171,15 @@ bool GossipMenu::IsMenuItemCoded(uint32 menuItemId) const
return itr->second.IsCoded;
}
+bool GossipMenu::HasMenuItemType(uint32 optionType) const
+{
+ for (auto const& menuItemPair : _menuItems)
+ if (menuItemPair.second.OptionType == optionType)
+ return true;
+
+ return false;
+}
+
void GossipMenu::ClearMenu()
{
_menuItems.clear();
diff --git a/src/server/game/Entities/Creature/GossipDef.h b/src/server/game/Entities/Creature/GossipDef.h
index b3e58081400..0916c88083a 100644
--- a/src/server/game/Entities/Creature/GossipDef.h
+++ b/src/server/game/Entities/Creature/GossipDef.h
@@ -49,7 +49,7 @@ enum Gossip_Option
GOSSIP_OPTION_STABLEPET = 14, //UNIT_NPC_FLAG_STABLE (4194304)
GOSSIP_OPTION_ARMORER = 15, //UNIT_NPC_FLAG_ARMORER (4096)
GOSSIP_OPTION_UNLEARNTALENTS = 16, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER)
- GOSSIP_OPTION_UNLEARNPETTALENTS = 17, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER)
+ GOSSIP_OPTION_UNLEARNPETTALENTS_OLD = 17, // deprecated
GOSSIP_OPTION_LEARNDUALSPEC = 18, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER)
GOSSIP_OPTION_OUTDOORPVP = 19, //added by code (option for outdoor pvp creatures)
GOSSIP_OPTION_MAX
@@ -146,6 +146,8 @@ struct GossipMenuItemData
{
uint32 GossipActionMenuId; // MenuId of the gossip triggered by this action
uint32 GossipActionPoi;
+
+ uint32 TrainerId;
};
// need an ordered container
@@ -175,7 +177,7 @@ class TC_GAME_API GossipMenu
void SetLocale(LocaleConstant locale) { _locale = locale; }
LocaleConstant GetLocale() const { return _locale; }
- void AddGossipMenuItemData(uint32 menuItemId, uint32 gossipActionMenuId, uint32 gossipActionPoi);
+ void AddGossipMenuItemData(uint32 optionIndex, uint32 gossipActionMenuId, uint32 gossipActionPoi, uint32 trainerId);
uint32 GetMenuItemCount() const { return uint32(_menuItems.size()); }
bool Empty() const { return _menuItems.empty(); }
@@ -201,6 +203,7 @@ class TC_GAME_API GossipMenu
uint32 GetMenuItemSender(uint32 menuItemId) const;
uint32 GetMenuItemAction(uint32 menuItemId) const;
bool IsMenuItemCoded(uint32 menuItemId) const;
+ bool HasMenuItemType(uint32 optionType) const;
void ClearMenu();
diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp
new file mode 100644
index 00000000000..013d8c73453
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2008-2017 TrinityCore <http://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
+{
+
+ Trainer::Trainer(uint32 id, Type type, std::string greeting, std::vector<Spell> spells) : _id(id), _type(type), _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.TrainerID = _id;
+ trainerList.Greeting = GetGreeting(locale);
+ trainerList.Spells.reserve(_spells.size());
+ for (Spell const& trainerSpell : _spells)
+ {
+ if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId))
+ continue;
+
+ trainerList.Spells.emplace_back();
+ WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back();
+ trainerListSpell.SpellID = trainerSpell.SpellId;
+ trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount);
+ trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine;
+ trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank;
+ std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin());
+ trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell));
+ trainerListSpell.ReqLevel = trainerSpell.ReqLevel;
+ }
+
+ player->SendDirectMessage(trainerList.Write());
+ }
+
+ void Trainer::TeachSpell(Creature const* npc, Player* player, uint32 spellId) const
+ {
+ Spell const* trainerSpell = GetSpell(spellId);
+ if (!trainerSpell || !CanTeachSpell(player, trainerSpell))
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::Unavailable);
+ return;
+ }
+
+ float reputationDiscount = player->GetReputationPriceDiscount(npc);
+ int64 moneyCost = int64(trainerSpell->MoneyCost * reputationDiscount);
+ if (!player->HasEnoughMoney(moneyCost))
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney);
+ return;
+ }
+
+ player->ModifyMoney(-moneyCost);
+
+ npc->SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected
+ player->SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
+
+ // learn explicitly or cast explicitly
+ if (trainerSpell->CastSpellId)
+ player->CastSpell(player, trainerSpell->CastSpellId, true);
+ else
+ player->LearnSpell(trainerSpell->SpellId, false);
+ }
+
+ 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 additional spell requirement
+ for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId))
+ if (!player->HasSpell(requirePair.second))
+ return SpellState::Unavailable;
+
+ return SpellState::Available;
+ }
+
+ void Trainer::SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const
+ {
+ WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed;
+ trainerBuyFailed.TrainerGUID = npc->GetGUID();
+ trainerBuyFailed.SpellID = spellId;
+ trainerBuyFailed.TrainerFailedReason = AsUnderlyingType(reason);
+ player->SendDirectMessage(trainerBuyFailed.Write());
+ }
+
+ std::string const& Trainer::GetGreeting(LocaleConstant locale) const
+ {
+ if (_greeting[locale].empty())
+ return _greeting[DEFAULT_LOCALE];
+
+ return _greeting[locale];
+ }
+
+ void Trainer::AddGreetingLocale(LocaleConstant locale, std::string 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..82c1096fa67
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008-2017 TrinityCore <http://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 <string>
+#include <vector>
+
+class Creature;
+class ObjectMgr;
+class Player;
+
+namespace Trainer
+{
+ enum class Type : uint32
+ {
+ None = 0,
+ Talent = 1,
+ Tradeskill = 2,
+ Pet = 3,
+ };
+
+ enum class SpellState : uint8
+ {
+ Known = 0,
+ Available = 1,
+ Unavailable = 2
+ };
+
+ enum class FailReason : uint32
+ {
+ Unavailable = 0,
+ NotEnoughMoney = 1
+ };
+
+ struct Spell
+ {
+ uint32 SpellId = 0;
+ uint32 MoneyCost = 0;
+ uint32 ReqSkillLine = 0;
+ uint32 ReqSkillRank = 0;
+ std::array<uint32, 3> ReqAbility = { };
+ uint8 ReqLevel = 0;
+
+ uint32 CastSpellId = 0;
+ };
+
+ class Trainer
+ {
+ public:
+ Trainer(uint32 id, Type type, 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;
+
+ 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;
+ std::string const& GetGreeting(LocaleConstant locale) const;
+
+ friend class ObjectMgr;
+ void AddGreetingLocale(LocaleConstant locale, std::string greeting);
+
+ uint32 _id;
+ Type _type;
+ 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 5c0bba68298..1510ac718d9 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -2270,19 +2270,19 @@ void Object::ForceValuesUpdateAtIndex(uint32 i)
AddToObjectUpdateIfNeeded();
}
-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 d01c4f6cd9e..7b21be71a39 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -464,9 +464,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 351f75b47e7..682c8624392 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -3648,74 +3648,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_TRAINERSPELL_ABILITY_REQS; ++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_TRAINERSPELL_ABILITY_REQS; ++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 (prevSpell && !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_TRAINERSPELL_ABILITY_REQS; ++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
*
@@ -5913,7 +5845,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)
GetSession()->SendPacket(data);
@@ -5922,7 +5854,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)
GetSession()->SendPacket(data);
@@ -5931,7 +5863,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)
GetSession()->SendPacket(data);
@@ -9340,27 +9272,6 @@ void Player::SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost) const
GetSession()->SendPacket(respecWipeConfirm.Write());
}
-void Player::ResetPetTalents()
-{
- /* TODO: 6.x remove/update pet talents
- // This needs another gossip option + NPC text as a confirmation.
- // The confirmation gossip listid has the text: "Yes, please do."
- Pet* pet = GetPet();
-
- if (!pet || pet->getPetType() != HUNTER_PET || pet->m_usedTalentCount == 0)
- return;
-
- CharmInfo* charmInfo = pet->GetCharmInfo();
- if (!charmInfo)
- {
- TC_LOG_ERROR("entities.player", "Object (%s) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().ToString().c_str());
- return;
- }
- pet->resetTalents();
- SendTalentsInfoData(true);
- */
-}
-
/*********************************************************/
/*** STORAGE SYSTEM ***/
/*********************************************************/
@@ -14167,10 +14078,10 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
for (GossipMenuItemsContainer::const_iterator itr = menuItemBounds.first; itr != menuItemBounds.second; ++itr)
{
- bool canTalk = true;
if (!sConditionMgr->IsObjectMeetToConditions(this, source, itr->second.Conditions))
continue;
+ bool canTalk = true;
if (Creature* creature = source->ToCreature())
{
if (!(itr->second.OptionNpcflag & npcflags))
@@ -14199,11 +14110,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
- if (!creature->isCanTrainingAndResetTalentsOf(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 (!creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_TAXIVENDOR:
@@ -14221,16 +14128,8 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
case GOSSIP_OPTION_QUESTGIVER:
canTalk = false;
break;
- case GOSSIP_OPTION_TRAINER:
- if (getClass() != creature->GetCreatureTemplate()->trainer_class && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS)
- {
- TC_LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player %s (%s) request wrong gossip menu: %u with wrong class: %u at Creature: %s (Entry: %u, Trainer Class: %u)",
- GetName().c_str(), GetGUID().ToString().c_str(), menu->GetGossipMenu().GetMenuId(), getClass(),
- creature->GetName().c_str(), creature->GetEntry(), creature->GetCreatureTemplate()->trainer_class);
- canTalk = false;
- }
- break;
case GOSSIP_OPTION_GOSSIP:
+ case GOSSIP_OPTION_TRAINER:
case GOSSIP_OPTION_SPIRITGUIDE:
case GOSSIP_OPTION_INNKEEPER:
case GOSSIP_OPTION_BANKER:
@@ -14297,7 +14196,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
}
menu->GetGossipMenu().AddMenuItem(itr->second.OptionIndex, itr->second.OptionIcon, strOptionText, 0, itr->second.OptionType, strBoxText, itr->second.BoxMoney, itr->second.BoxCoded);
- menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionIndex, itr->second.ActionMenuId, itr->second.ActionPoiId);
+ menu->GetGossipMenu().AddGossipMenuItemData(itr->second.OptionIndex, itr->second.ActionMenuId, itr->second.ActionPoiId, itr->second.TrainerId);
}
}
}
@@ -14398,7 +14297,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men
GetSession()->SendStablePet(guid);
break;
case GOSSIP_OPTION_TRAINER:
- GetSession()->SendTrainerList(guid);
+ GetSession()->SendTrainerList(guid, menuItemData->TrainerId);
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
break;
@@ -14406,10 +14305,6 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men
PlayerTalkClass->SendCloseGossip();
SendRespecWipeConfirm(guid, sWorld->getBoolConfig(CONFIG_NO_RESET_TALENT_COST) ? 0 : GetNextResetTalentsCost());
break;
- case GOSSIP_OPTION_UNLEARNPETTALENTS:
- PlayerTalkClass->SendCloseGossip();
- ResetPetTalents();
- break;
case GOSSIP_OPTION_TAXIVENDOR:
GetSession()->SendTaxiMenu(source->ToCreature());
break;
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 6a8d1a09419..b96a44eb310 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -242,14 +242,6 @@ typedef std::unordered_map<uint32, PlayerCurrency> PlayerCurrenciesMap;
typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap;
-enum TrainerSpellState
-{
- TRAINER_SPELL_GRAY = 0,
- TRAINER_SPELL_GREEN = 1,
- TRAINER_SPELL_RED = 2,
- TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed
-};
-
enum ActionButtonUpdateState
{
ACTIONBUTTON_UNCHANGED = 0,
@@ -1506,7 +1498,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetBindPoint(ObjectGuid guid) const;
void SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost) const;
- void ResetPetTalents();
void RegenerateAll();
void Regenerate(Powers power);
void RegenerateHealth();
@@ -1576,7 +1567,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool HasSpell(uint32 spell) const override;
bool HasActiveSpell(uint32 spell) const; // show in spellbook
SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo) const override;
- TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
bool IsSpellFitByClassAndRace(uint32 spell_id) const;
bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const;
bool IsCurrentSpecMasterySpell(SpellInfo const* spellInfo) const;
@@ -1852,10 +1842,10 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool UpdatePosition(const Position &pos, bool teleport = false) override { return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); }
void UpdateUnderwaterState(Map* m, float x, float y, float z) 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);
@@ -2347,6 +2337,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
SceneMgr& GetSceneMgr() { return m_sceneMgr; }
RestMgr& GetRestMgr() const { return *_restMgr; }
+ uint32 GetCurrentTrainerId() const { return _currentTrainerId; }
+ void SetCurrentTrainerId(uint32 trainerId) { _currentTrainerId = trainerId; }
protected:
// Gamemaster whisper whitelist
GuidList WhisperList;
@@ -2698,6 +2690,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void _InitHonorLevelOnLoadFromDB(uint32 /*honor*/, uint32 /*honorLevel*/, uint32 /*prestigeLevel*/);
std::unique_ptr<RestMgr> _restMgr;
+
+ uint32 _currentTrainerId;
};
TC_GAME_API void AddItemsSetItem(Player* player, Item* item);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 8d8371e28d4..4d1c88b2a8b 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -12743,7 +12743,7 @@ void Unit::SendCancelSpellVisualKit(uint32 id)
SendMessageToSet(cancelSpellVisualKit.Write(), true);
}
-void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration)
+void Unit::SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const
{
WorldPackets::Spells::PlaySpellVisualKit playSpellVisualKit;
playSpellVisualKit.Unit = GetGUID();
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index b782ccfd288..cd56f883e7a 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1324,7 +1324,7 @@ class TC_GAME_API Unit : public WorldObject
void SendPlaySpellVisual(ObjectGuid const& target, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime = false);
void SendPlaySpellVisual(Position const& targetPosition, float o, uint32 spellVisualId, uint16 missReason, uint16 reflectStatus, float travelSpeed, bool speedAsTime = false);
void SendCancelSpellVisualKit(uint32 id);
- void SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration);
+ void SendPlaySpellVisualKit(uint32 id, uint32 type, uint32 duration) const;
void DeMorph();
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 89fb1ab8a01..2efdbfb015b 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -19,6 +19,7 @@
#include "ObjectMgr.h"
#include "ArenaTeamMgr.h"
#include "Chat.h"
+#include "Containers.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
#include "DisableMgr.h"
@@ -285,8 +286,6 @@ ObjectMgr::~ObjectMgr()
for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr)
itr->second.Clear();
- _cacheTrainerSpellStore.clear();
-
for (DungeonEncounterContainer::iterator itr =_dungeonEncounterStore.begin(); itr != _dungeonEncounterStore.end(); ++itr)
for (DungeonEncounterList::iterator encounterItr = itr->second.begin(); encounterItr != itr->second.end(); ++encounterItr)
delete *encounterItr;
@@ -425,15 +424,15 @@ void ObjectMgr::LoadCreatureTemplates()
"modelid4, name, femaleName, subname, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, "
// 20 21 22 23 24 25 26 27 28 29 30
"faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, "
- // 31 32 33 34 35 36 37 38 39 40
- "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_type, trainer_class, trainer_race, type, "
- // 41 42 43 44 45 46 47 48 49 50 51
+ // 31 32 33 34 35 36 37 38
+ "unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, "
+ // 39 40 41 42 43 44 45 46 47 48 49
"type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, "
- // 52 53 54 55 56 57 58 59 60 61 62 63 64
+ // 50 51 52 53 54 55 56 57 58 59 60 61 62
"spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, "
- // 65 66 67 68 69 70 71 72 73
+ // 63 64 65 66 67 68 69 70 71
"InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, "
- // 74 75 76 77 78 79
+ // 72 73 74 75 76 77
"RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template");
if (!result)
@@ -504,42 +503,40 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields)
creatureTemplate.unit_flags3 = fields[34].GetUInt32();
creatureTemplate.dynamicflags = fields[35].GetUInt32();
creatureTemplate.family = CreatureFamily(fields[36].GetUInt8());
- creatureTemplate.trainer_type = uint32(fields[37].GetUInt8());
- creatureTemplate.trainer_class = uint32(fields[38].GetUInt8());
- creatureTemplate.trainer_race = uint32(fields[39].GetUInt8());
- creatureTemplate.type = uint32(fields[40].GetUInt8());
- creatureTemplate.type_flags = fields[41].GetUInt32();
- creatureTemplate.type_flags2 = fields[42].GetUInt32();
- creatureTemplate.lootid = fields[43].GetUInt32();
- creatureTemplate.pickpocketLootId = fields[44].GetUInt32();
- creatureTemplate.SkinLootId = fields[45].GetUInt32();
+ creatureTemplate.trainer_class = uint32(fields[37].GetUInt8());
+ creatureTemplate.type = uint32(fields[38].GetUInt8());
+ creatureTemplate.type_flags = fields[39].GetUInt32();
+ creatureTemplate.type_flags2 = fields[40].GetUInt32();
+ creatureTemplate.lootid = fields[41].GetUInt32();
+ creatureTemplate.pickpocketLootId = fields[42].GetUInt32();
+ creatureTemplate.SkinLootId = fields[43].GetUInt32();
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
- creatureTemplate.resistance[i] = fields[46 + i - 1].GetInt16();
+ creatureTemplate.resistance[i] = fields[44 + i - 1].GetInt16();
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
- creatureTemplate.spells[i] = fields[52 + i].GetUInt32();
-
- creatureTemplate.VehicleId = fields[60].GetUInt32();
- creatureTemplate.mingold = fields[61].GetUInt32();
- creatureTemplate.maxgold = fields[62].GetUInt32();
- creatureTemplate.AIName = fields[63].GetString();
- creatureTemplate.MovementType = uint32(fields[64].GetUInt8());
- creatureTemplate.InhabitType = uint32(fields[65].GetUInt8());
- creatureTemplate.HoverHeight = fields[66].GetFloat();
- creatureTemplate.ModHealth = fields[67].GetFloat();
- creatureTemplate.ModHealthExtra = fields[68].GetFloat();
- creatureTemplate.ModMana = fields[69].GetFloat();
- creatureTemplate.ModManaExtra = fields[70].GetFloat();
- creatureTemplate.ModArmor = fields[71].GetFloat();
- creatureTemplate.ModDamage = fields[72].GetFloat();
- creatureTemplate.ModExperience = fields[73].GetFloat();
- creatureTemplate.RacialLeader = fields[74].GetBool();
- creatureTemplate.movementId = fields[75].GetUInt32();
- creatureTemplate.RegenHealth = fields[76].GetBool();
- creatureTemplate.MechanicImmuneMask = fields[77].GetUInt32();
- creatureTemplate.flags_extra = fields[78].GetUInt32();
- creatureTemplate.ScriptID = GetScriptId(fields[79].GetString());
+ creatureTemplate.spells[i] = fields[50 + i].GetUInt32();
+
+ creatureTemplate.VehicleId = fields[58].GetUInt32();
+ creatureTemplate.mingold = fields[59].GetUInt32();
+ creatureTemplate.maxgold = fields[60].GetUInt32();
+ creatureTemplate.AIName = fields[61].GetString();
+ creatureTemplate.MovementType = uint32(fields[62].GetUInt8());
+ creatureTemplate.InhabitType = uint32(fields[63].GetUInt8());
+ creatureTemplate.HoverHeight = fields[64].GetFloat();
+ creatureTemplate.ModHealth = fields[65].GetFloat();
+ creatureTemplate.ModHealthExtra = fields[66].GetFloat();
+ creatureTemplate.ModMana = fields[67].GetFloat();
+ creatureTemplate.ModManaExtra = 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.flags_extra = fields[76].GetUInt32();
+ creatureTemplate.ScriptID = GetScriptId(fields[77].GetString());
}
void ObjectMgr::LoadCreatureTemplateAddons()
@@ -772,24 +769,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
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->type != difficultyInfo->type)
{
TC_LOG_ERROR("sql.sql", "Creature (Entry: %u, type: %u) has different `type` in difficulty %u mode (Entry: %u, type: %u).",
@@ -953,9 +932,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);
@@ -8494,118 +8470,126 @@ 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, uint32 Index)
+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 any 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 spell (Spell: %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 spell (Spell: %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;
+ }
- 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;
- trainerSpell.Index = Index;
+ 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, spell.SpellId, trainerId);
+ allReqValid = false;
+ }
+ }
- if (!trainerSpell.ReqLevel)
- trainerSpell.ReqLevel = spellinfo->SpellLevel;
+ if (!allReqValid)
+ continue;
- // calculate learned spell for profession case when stored cast-spell
- trainerSpell.ReqAbility[0] = SpellID;
- for (SpellEffectInfo const* effect : spellinfo->GetEffectsForDifficulty(DIFFICULTY_NONE))
- {
- if (!effect || effect->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 (effect->TargetA.GetTarget() != 0 && effect->TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY
- && effect->TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && effect->TargetA.GetTarget() != TARGET_UNIT_CASTER)
- {
- 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;
- }
+ for (SpellEffectInfo const* spellEffect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE))
+ {
+ if (spellEffect->IsEffect(SPELL_EFFECT_LEARN_SPELL))
+ {
+ spell.CastSpellId = spell.SpellId;
+ spell.SpellId = spellEffect->TriggerSpell;
+ break;
+ }
+ }
- trainerSpell.ReqAbility[effect->EffectIndex] = effect->TriggerSpell;
+ spellsByTrainer[trainerId].push_back(spell);
- if (trainerSpell.ReqAbility[effect->EffectIndex])
- {
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.ReqAbility[effect->EffectIndex]);
- if (learnedSpellInfo && learnedSpellInfo->IsProfession())
- data.trainerType = 2;
- }
+ } while (trainerSpellsResult->NextRow());
}
- return;
-}
-
-void ObjectMgr::LoadTrainerSpell()
-{
- uint32 oldMSTime = getMSTime();
+ if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Greeting FROM trainer"))
+ {
+ do
+ {
+ Field* fields = trainersResult->Fetch();
+ uint32 trainerId = fields[0].GetUInt32();
+ Trainer::Type trainerType = Trainer::Type(fields[1].GetUInt8());
+ std::string greeting = fields[2].GetString();
+ std::vector<Trainer::Spell> spells;
+ auto spellsItr = spellsByTrainer.find(trainerId);
+ if (spellsItr != spellsByTrainer.end())
+ {
+ spells = std::move(spellsItr->second);
+ spellsByTrainer.erase(spellsItr);
+ }
- // For reload case
- _cacheTrainerSpellStore.clear();
+ _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, std::move(greeting), std::move(spells)));
- QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.Reqlevel, a.Index FROM npc_trainer AS a "
- "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) "
- "UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
+ } while (trainersResult->NextRow());
+ }
- if (!result)
+ for (auto const& unusedSpells : spellsByTrainer)
{
- TC_LOG_ERROR("server.loading", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!");
-
- return;
+ for (Trainer::Spell const& unusedSpell : unusedSpells.second)
+ {
+ TC_LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: %u) for SpellId %u, ignoring", unusedSpells.first, unusedSpell.SpellId);
+ }
}
- uint32 count = 0;
-
- do
+ if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale"))
{
- Field* fields = result->Fetch();
+ do
+ {
+ Field* fields = trainerLocalesResult->Fetch();
+ uint32 trainerId = fields[0].GetUInt32();
+ std::string localeName = fields[1].GetString();
- 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();
- uint32 Index = fields[6].GetUInt8();
+ LocaleConstant locale = GetLocaleByName(localeName);
+ if (locale == LOCALE_enUS)
+ continue;
- AddSpellToTrainer(ID, SpellID, MoneyCost, ReqSkillLine, ReqSkillRank, ReqLevel, Index);
+ 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());
- ++count;
+ } while (trainerLocalesResult->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 " Trainers in %u ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime));
}
int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, uint8 referenceType, std::set<uint32> *skip_vendors)
@@ -8748,9 +8732,19 @@ void ObjectMgr::LoadGossipMenuItems()
_gossipMenuItemsStore.clear();
QueryResult result = WorldDatabase.Query(
- // 0 1 2 3 4 5 6 7 8 9 10 11 12
- "SELECT menu_id, id, option_icon, option_text, OptionBroadcastTextID, option_id, npc_option_npcflag, action_menu_id, action_poi_id, box_coded, box_money, box_text, BoxBroadcastTextID "
- "FROM gossip_menu_option ORDER BY menu_id, id");
+ // 0 1 2 3 4 5 6
+ "SELECT o.MenuId, o.OptionIndex, o.OptionIcon, o.OptionText, o.OptionBroadcastTextId, o.OptionType, o.OptionNpcflag, "
+ // 7 8
+ "oa.ActionMenuId, oa.ActionPoiId, "
+ // 9 10 11 12
+ "ob.BoxCoded, ob.BoxMoney, ob.BoxText, ob.BoxBroadcastTextId, "
+ // 13
+ "ot.TrainerId "
+ "FROM gossip_menu_option o "
+ "LEFT JOIN gossip_menu_option_action oa ON o.MenuId = oa.MenuId AND o.OptionIndex = oa.OptionIndex "
+ "LEFT JOIN gossip_menu_option_box ob ON o.MenuId = ob.MenuId AND o.OptionIndex = ob.OptionIndex "
+ "LEFT JOIN gossip_menu_option_trainer ot ON o.MenuId = ot.MenuId AND o.OptionIndex = ot.OptionIndex "
+ "ORDER BY o.MenuId, o.OptionIndex");
if (!result)
{
@@ -8766,12 +8760,12 @@ void ObjectMgr::LoadGossipMenuItems()
GossipMenuItems gMenuItem;
- gMenuItem.MenuId = fields[0].GetUInt16();
- gMenuItem.OptionIndex = fields[1].GetUInt16();
- gMenuItem.OptionIcon = fields[2].GetUInt32();
+ gMenuItem.MenuId = fields[0].GetUInt32();
+ gMenuItem.OptionIndex = fields[1].GetUInt32();
+ gMenuItem.OptionIcon = fields[2].GetUInt8();
gMenuItem.OptionText = fields[3].GetString();
gMenuItem.OptionBroadcastTextId = fields[4].GetUInt32();
- gMenuItem.OptionType = fields[5].GetUInt8();
+ gMenuItem.OptionType = fields[5].GetUInt32();
gMenuItem.OptionNpcflag = fields[6].GetUInt64();
gMenuItem.ActionMenuId = fields[7].GetUInt32();
gMenuItem.ActionPoiId = fields[8].GetUInt32();
@@ -8779,10 +8773,11 @@ void ObjectMgr::LoadGossipMenuItems()
gMenuItem.BoxMoney = fields[10].GetUInt32();
gMenuItem.BoxText = fields[11].GetString();
gMenuItem.BoxBroadcastTextId = fields[12].GetUInt32();
+ gMenuItem.TrainerId = fields[13].GetUInt32();
if (gMenuItem.OptionIcon >= GOSSIP_ICON_MAX)
{
- TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionIcon);
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionIcon);
gMenuItem.OptionIcon = GOSSIP_ICON_CHAT;
}
@@ -8790,17 +8785,17 @@ void ObjectMgr::LoadGossipMenuItems()
{
if (!sBroadcastTextStore.LookupEntry(gMenuItem.OptionBroadcastTextId))
{
- TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible OptionBroadcastTextId %u, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionBroadcastTextId);
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has non-existing or incompatible OptionBroadcastTextId %u, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionBroadcastTextId);
gMenuItem.OptionBroadcastTextId = 0;
}
}
if (gMenuItem.OptionType >= GOSSIP_OPTION_MAX)
- TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has unknown option id %u. Option will not be used", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionType);
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has unknown option id %u. Option will not be used", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.OptionType);
if (gMenuItem.ActionPoiId && !GetPointOfInterest(gMenuItem.ActionPoiId))
{
- TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u use non-existing action_poi_id %u, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId);
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u use non-existing action_poi_id %u, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.ActionPoiId);
gMenuItem.ActionPoiId = 0;
}
@@ -8808,11 +8803,17 @@ void ObjectMgr::LoadGossipMenuItems()
{
if (!sBroadcastTextStore.LookupEntry(gMenuItem.BoxBroadcastTextId))
{
- TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for menu %u, id %u has non-existing or incompatible BoxBroadcastTextId %u, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.BoxBroadcastTextId);
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option` for MenuId %u, OptionIndex %u has non-existing or incompatible BoxBroadcastTextId %u, ignoring.", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.BoxBroadcastTextId);
gMenuItem.BoxBroadcastTextId = 0;
}
}
+ if (gMenuItem.TrainerId && !GetTrainer(gMenuItem.TrainerId))
+ {
+ TC_LOG_ERROR("sql.sql", "Table `gossip_menu_option_trainer` for MenuId %u, OptionIndex %u use non-existing TrainerId %u, ignoring", gMenuItem.MenuId, gMenuItem.OptionIndex, gMenuItem.TrainerId);
+ gMenuItem.TrainerId = 0;
+ }
+
_gossipMenuItemsStore.insert(GossipMenuItemsContainer::value_type(gMenuItem.MenuId, gMenuItem));
++count;
}
@@ -8821,6 +8822,11 @@ void ObjectMgr::LoadGossipMenuItems()
TC_LOG_INFO("server.loading", ">> Loaded %u gossip_menu_option entries in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
}
+Trainer::Trainer const* ObjectMgr::GetTrainer(uint32 trainerId) const
+{
+ return Trinity::Containers::MapGetValuePtr(_trainers, trainerId);
+}
+
void ObjectMgr::AddVendorItem(uint32 entry, uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedCost, uint8 type, bool persist /*= true*/)
{
VendorItemData& vList = _cacheVendorItemStore[entry];
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
index 18bfaec87cb..678d0df64e6 100644
--- a/src/server/game/Globals/ObjectMgr.h
+++ b/src/server/game/Globals/ObjectMgr.h
@@ -31,6 +31,7 @@
#include "Position.h"
#include "QuestDef.h"
#include "SharedDefines.h"
+#include "Trainer.h"
#include "VehicleDefines.h"
#include <map>
#include <unordered_map>
@@ -662,6 +663,7 @@ struct GossipMenuItems
uint32 BoxMoney;
std::string BoxText;
uint32 BoxBroadcastTextId;
+ uint32 TrainerId;
ConditionContainer Conditions;
};
@@ -737,7 +739,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;
typedef std::unordered_map<uint32, std::string> RealmNameContainer;
@@ -1163,8 +1164,7 @@ class TC_GAME_API ObjectMgr
void LoadGossipMenuItems();
void LoadVendors();
- void LoadTrainerSpell();
- void AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 Index);
+ void LoadTrainers();
void LoadTerrainPhaseInfo();
void LoadTerrainSwapDefaults();
@@ -1377,14 +1377,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 trainerId) const;
VendorItemData const* GetNpcVendorItemList(uint32 entry) const
{
@@ -1664,7 +1657,7 @@ class TC_GAME_API ObjectMgr
TrinityStringContainer _trinityStringStore;
CacheVendorItemContainer _cacheVendorItemStore;
- CacheTrainerSpellContainer _cacheTrainerSpellStore;
+ std::unordered_map<uint32, Trainer::Trainer> _trainers;
std::set<uint32> _difficultyEntries[MAX_CREATURE_DIFFICULTIES]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
std::set<uint32> _hasDifficultyEntries[MAX_CREATURE_DIFFICULTIES]; // 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 2de7397ac14..0e5c2350736 100644
--- a/src/server/game/Grids/Notifiers/GridNotifiers.h
+++ b/src/server/game/Grids/Notifiers/GridNotifiers.h
@@ -119,18 +119,18 @@ namespace Trinity
struct TC_GAME_API MessageDistDeliverer
{
- WorldObject* i_source;
+ WorldObject const* i_source;
WorldPacket const* i_message;
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 = NULL)
+ 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_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 82f7336f0e4..57ad947e1ef 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -44,6 +44,7 @@
#include "ScriptMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
+#include "Trainer.h"
#include "WorldPacket.h"
enum StableResultCode
@@ -89,16 +90,10 @@ void WorldSession::SendShowMailBox(ObjectGuid guid)
void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet)
{
- SendTrainerList(packet.Unit);
+ TC_LOG_INFO("network", "%s sent legacy gossipless trainer hello for unit %s, no trainer list available", GetPlayerInfo().c_str(), packet.Unit.ToString().c_str());
}
-void WorldSession::SendTrainerList(ObjectGuid guid, uint32 index)
-{
- std::string str = GetTrinityString(LANG_NPC_TAINER_HELLO);
- SendTrainerList(guid, str, index);
-}
-
-void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle, uint32 index)
+void WorldSession::SendTrainerList(ObjectGuid guid, uint32 trainerId)
{
Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
if (!unit)
@@ -111,92 +106,23 @@ void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle,
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(trainerId);
+ if (!trainer)
{
- TC_LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for %s", guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: SendTrainerList - trainer spells not found for trainer %s id %d", guid.ToString().c_str(), trainerId);
return;
}
- WorldPackets::NPC::TrainerList packet;
- packet.TrainerGUID = guid;
- packet.TrainerType = trainer_spells->trainerType;
- packet.Greeting = strTitle;
-
- // reputation discount
- float fDiscountMod = _player->GetReputationPriceDiscount(unit);
-
- packet.Spells.reserve(trainer_spells->spellList.size());
- for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr)
- {
- TrainerSpell const* tSpell = &itr->second;
-
- if (index && tSpell->Index != index)
- continue;
-
- bool valid = true;
- for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i)
- {
- if (!tSpell->ReqAbility[i])
- continue;
- if (!_player->IsSpellFitByClassAndRace(tSpell->ReqAbility[i]))
- {
- valid = false;
- break;
- }
- }
-
- if (!valid)
- continue;
-
- TrainerSpellState state = _player->GetTrainerSpellState(tSpell);
-
- WorldPackets::NPC::TrainerListSpell spell;
- spell.SpellID = tSpell->SpellID;
- spell.MoneyCost = floor(tSpell->MoneyCost * fDiscountMod);
- spell.ReqSkillLine = tSpell->ReqSkillLine;
- spell.ReqSkillRank = tSpell->ReqSkillRank;
- spell.ReqLevel = tSpell->ReqLevel;
- spell.Usable = (state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state);
-
- uint8 maxReq = 0;
- for (uint8 i = 0; i < MAX_TRAINERSPELL_ABILITY_REQS; ++i)
- {
- if (!tSpell->ReqAbility[i])
- continue;
-
- if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->ReqAbility[i]))
- {
- spell.ReqAbility[maxReq] = prevSpellId;
- ++maxReq;
- }
-
- if (maxReq == 2)
- break;
-
- SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->ReqAbility[i]);
- for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequired.first; itr2 != spellsRequired.second && maxReq < MAX_TRAINERSPELL_ABILITY_REQS; ++itr2)
- {
- spell.ReqAbility[maxReq] = itr2->second;
- ++maxReq;
- }
-
- if (maxReq == 2)
- break;
- }
-
- packet.Spells.push_back(spell);
- }
-
- SendPacket(packet.Write());
+ _player->SetCurrentTrainerId(trainerId);
+ trainer->SendSpells(unit, _player, GetSessionDbLocaleIndex());
}
void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL %s, learn spell id is: %i", packet.TrainerGUID.ToString().c_str(), packet.SpellID);
- Creature* trainer = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, 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.", packet.TrainerGUID.ToString().c_str());
return;
@@ -206,70 +132,14 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpel
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)
- {
- SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
+ if (_player->GetCurrentTrainerId() != packet.TrainerID)
return;
- }
- // not found, cheat?
- TrainerSpell const* trainerSpell = trainer_spells->Find(packet.SpellID);
- if (!trainerSpell)
- {
- SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(packet.TrainerID);
+ if (!npc)
return;
- }
- // can't be learn, cheat? Or double learn with lags...
- if (_player->GetTrainerSpellState(trainerSpell) != TRAINER_SPELL_GREEN)
- {
- SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 0);
- return;
- }
-
- // apply reputation discount
- uint32 nSpellCost = uint32(floor(trainerSpell->MoneyCost * _player->GetReputationPriceDiscount(trainer)));
-
- // check money requirement
- if (!_player->HasEnoughMoney(uint64(nSpellCost)))
- {
- SendTrainerBuyFailed(packet.TrainerGUID, packet.SpellID, 1);
- return;
- }
-
- _player->ModifyMoney(-int64(nSpellCost));
-
- trainer->SendPlaySpellVisualKit(179, 0, 0); // 53 SpellCastDirected
- _player->SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute
-
- // learn explicitly or cast explicitly
- if (trainerSpell->IsCastable())
- _player->CastSpell(_player, trainerSpell->SpellID, true);
- else
- _player->LearnSpell(packet.SpellID, false);
-}
-
-void WorldSession::SendTrainerBuyFailed(ObjectGuid trainerGUID, uint32 spellID, int32 trainerFailedReason)
-{
- WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed;
- trainerBuyFailed.TrainerGUID = trainerGUID;
- trainerBuyFailed.SpellID = spellID; // should be same as in packet from client
- trainerBuyFailed.TrainerFailedReason = trainerFailedReason; // 1 == "Not enough money for trainer service." 0 == "Trainer service %d unavailable."
- SendPacket(trainerBuyFailed.Write());
+ trainer->TeachSpell(npc, _player, packet.SpellID);
}
void WorldSession::HandleGossipHelloOpcode(WorldPackets::NPC::Hello& packet)
diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp
index b3dc78e3484..cbc49c50c22 100644
--- a/src/server/game/Handlers/SkillHandler.cpp
+++ b/src/server/game/Handlers/SkillHandler.cpp
@@ -19,6 +19,7 @@
#include "WorldSession.h"
#include "Common.h"
#include "DB2Stores.h"
+#include "GossipDef.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "Pet.h"
@@ -65,7 +66,10 @@ void WorldSession::HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRe
return;
}
- if (!unit->isCanTrainingAndResetTalentsOf(_player))
+ if (!unit->CanResetTalents(_player))
+ return;
+
+ if (!_player->PlayerTalkClass->GetGossipMenu().HasMenuItemType(GOSSIP_OPTION_UNLEARNTALENTS))
return;
// remove fake death
diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h
index 4724f8fd1f2..fd7acfb72db 100644
--- a/src/server/game/Miscellaneous/SharedDefines.h
+++ b/src/server/game/Miscellaneous/SharedDefines.h
@@ -164,7 +164,7 @@ enum Races
#define RACEMASK_HORDE RACEMASK_ALL_PLAYABLE & ~RACEMASK_ALLIANCE
// Class value is index in ChrClasses.dbc
-enum Classes
+enum Classes : uint8
{
CLASS_NONE = 0,
CLASS_WARRIOR = 1,
@@ -3801,13 +3801,8 @@ enum LockType
enum TrainerType
{
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 (6.0.2.18988)
enum CreatureType
{
diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h
index d0d8a42beeb..f5affe0f484 100644
--- a/src/server/game/Server/Packets/NPCPackets.h
+++ b/src/server/game/Server/Packets/NPCPackets.h
@@ -145,11 +145,11 @@ namespace WorldPackets
WorldPacket const* Write() override;
- std::string Greeting;
- int32 TrainerType = 0;
ObjectGuid TrainerGUID;
+ int32 TrainerType = 0;
int32 TrainerID = 1;
std::vector<TrainerListSpell> Spells;
+ std::string Greeting;
};
class ShowBank final : public ServerPacket
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index ae51478180e..6899bf97b95 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -971,8 +971,7 @@ class TC_GAME_API WorldSession
void SendNameQueryOpcode(ObjectGuid guid);
- void SendTrainerList(ObjectGuid guid, uint32 index = 0);
- void SendTrainerList(ObjectGuid guid, std::string const& strTitle, uint32 index = 0);
+ void SendTrainerList(ObjectGuid guid, uint32 trainerId);
void SendListInventory(ObjectGuid guid);
void SendShowBank(ObjectGuid guid);
bool CanOpenMailBox(ObjectGuid guid);
@@ -1342,7 +1341,6 @@ class TC_GAME_API WorldSession
void HandleStableRevivePet(WorldPacket& recvPacket);
void HandleStableSwapPet(WorldPacket& recvPacket);
void HandleStableSwapPetCallback(uint32 petId, PreparedQueryResult result);
- void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint32 spellID, int32 trainerFailedReason);
void HandleCanDuel(WorldPackets::Duel::CanDuel& packet);
void HandleDuelResponseOpcode(WorldPackets::Duel::DuelResponse& duelResponse);
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index 3b8f118a827..5258e1532f0 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -607,9 +607,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 2d8b4326454..c3d493357c1 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"
@@ -664,7 +665,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 7d57d7de22c..78212696331 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1937,17 +1937,17 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading GameTeleports...");
sObjectMgr->LoadGameTele();
+ TC_LOG_INFO("server.loading", "Loading Trainers...");
+ sObjectMgr->LoadTrainers(); // must be after load CreatureTemplate
+
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
+ sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
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 48a7106fc2d..fa494b6d709 100644
--- a/src/server/scripts/Commands/cs_reload.cpp
+++ b/src/server/scripts/Commands/cs_reload.cpp
@@ -122,7 +122,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_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, "" },
{ "support", rbac::RBAC_PERM_COMMAND_RELOAD_SUPPORT_SYSTEM, true, &HandleReloadSupportSystemCommand, "" },
+ { "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, "" },
@@ -242,7 +242,7 @@ public:
static bool HandleReloadAllNpcCommand(ChatHandler* handler, const char* 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");
@@ -691,11 +691,13 @@ public:
return true;
}
- static bool HandleReloadNpcTrainerCommand(ChatHandler* handler, const char* /*args*/)
+ static bool HandleReloadTrainerCommand(ChatHandler* handler, const char* /*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();
+ handler->SendGlobalGMSysMessage("DB table `trainer` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_locale` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_spell` reloaded.");
return true;
}