aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/Chat/LanguageMgr.cpp111
-rw-r--r--src/server/game/Chat/LanguageMgr.h20
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp20
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp9
-rw-r--r--src/server/game/DataStores/DB2Stores.h1
-rw-r--r--src/server/game/Entities/Player/Player.cpp11
-rw-r--r--src/server/game/Entities/Player/Player.h2
-rw-r--r--src/server/game/Handlers/ChatHandler.cpp19
-rw-r--r--src/server/game/Texts/ChatTextBuilder.cpp6
-rw-r--r--src/server/game/Texts/ChatTextBuilder.h2
-rw-r--r--src/server/game/Texts/CreatureTextMgr.cpp2
-rw-r--r--src/server/game/World/World.cpp15
-rw-r--r--src/server/scripts/Commands/cs_learn.cpp4
13 files changed, 73 insertions, 149 deletions
diff --git a/src/server/game/Chat/LanguageMgr.cpp b/src/server/game/Chat/LanguageMgr.cpp
index acbd76f0a56..5e81ed35193 100644
--- a/src/server/game/Chat/LanguageMgr.cpp
+++ b/src/server/game/Chat/LanguageMgr.cpp
@@ -16,12 +16,12 @@
*/
#include "LanguageMgr.h"
+#include "Containers.h"
#include "DB2Stores.h"
#include "Log.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Timer.h"
-
#include <sstream>
LanguageMgr::LanguageMgr() : _langsMap(), _wordsMap() { }
@@ -43,37 +43,8 @@ void LanguageMgr::LoadSpellEffectLanguage(SpellEffectEntry const* spellEffect)
ASSERT(spellEffect && spellEffect->Effect == SPELL_EFFECT_LANGUAGE);
uint32 languageId = uint32(spellEffect->EffectMiscValue[0]);
- auto iter = _langsMap.find(languageId);
- if (iter == _langsMap.end())
- {
- TC_LOG_WARN("languages.spell", "LoadSpellEffectLanguage called on Spell %u with language %u which does not exist in Language.db2!",
- spellEffect->SpellID, languageId);
- return;
- }
- LanguageDesc& desc = iter->second;
- desc.SpellId = spellEffect->SpellID;
-}
-
-uint32 LanguageMgr::GetSpellLanguage(uint32 spellId) const
-{
- if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE))
- {
- SpellEffectInfoVector const& effects = spellInfo->GetEffects();
- if (effects.size() != 1 || effects[0]->Effect != SPELL_EFFECT_LANGUAGE)
- TC_LOG_WARN("languages.spell", "Invalid language spell %u. Expected 1 effect with SPELL_EFFECT_LANGUAGE", spellId);
- else
- return effects[0]->MiscValue;
- }
- return 0;
-}
-
-bool LanguageMgr::IsRelevantLanguageSkill(SkillLineEntry const* skillLineEntry) const
-{
- if (!skillLineEntry)
- return false;
- SkillRaceClassInfoEntry const* entry = sDB2Manager.GetAvailableSkillRaceClassInfo(skillLineEntry->ID);
- return entry != nullptr;
+ _langsMap.emplace(languageId, LanguageDesc{ spellEffect->SpellID, 0 }); // register without a skill id for now
}
void LanguageMgr::LoadLanguages()
@@ -82,57 +53,33 @@ void LanguageMgr::LoadLanguages()
// Load languages from Languages.db2. Just the id, we don't need the name
for (LanguagesEntry const* langEntry : sLanguagesStore)
- _langsMap.emplace(langEntry->ID, LanguageDesc());
-
- // Add the languages used in code in case they don't exist
- _langsMap.emplace(LANG_UNIVERSAL, LanguageDesc());
- _langsMap.emplace(LANG_ADDON, LanguageDesc());
- _langsMap.emplace(LANG_ADDON_LOGGED, LanguageDesc());
-
- // Log load time
- TC_LOG_INFO("server.loading", ">> Loaded %u languages in %u ms", uint32(_langsMap.size()), GetMSTimeDiffToNow(oldMSTime));
-}
-
-void LanguageMgr::LoadLanguagesSkills()
-{
- uint32 oldMSTime = getMSTime();
-
- uint32 count = 0;
- for (SkillLineEntry const* skillLineEntry : sSkillLineStore)
{
- if (skillLineEntry->CategoryID != SKILL_CATEGORY_LANGUAGES)
- continue;
-
- if (!IsRelevantLanguageSkill(skillLineEntry))
- continue;
-
- std::vector<SkillLineAbilityEntry const*> const* skills = sDB2Manager.GetSkillLineAbilitiesBySkill(skillLineEntry->ID);
-
- // We're expecting only 1 skill
- if (skills->size() != 1)
- TC_LOG_WARN("server.loading", "Found language skill line with %u spells. Expected 1. Will use 1st if available", uint32(skills->size()));
-
- if (SkillLineAbilityEntry const* ability = skills->empty() ? nullptr : skills->at(0))
+ auto spellsRange = Trinity::Containers::MapEqualRange(_langsMap, langEntry->ID);
+ if (spellsRange.begin() == spellsRange.end())
+ _langsMap.emplace(langEntry->ID, LanguageDesc());
+ else
{
- if (uint32 languageId = GetSpellLanguage(ability->Spell))
+ std::vector<LanguageDesc> langsWithSkill;
+ for (LanguagesMap::value_type const& spellItr : spellsRange)
+ for (SkillLineAbilityMap::value_type const& skillPair : Trinity::Containers::MakeIteratorPair(sSpellMgr->GetSkillLineAbilityMapBounds(spellItr.second.SpellId)))
+ langsWithSkill.emplace_back(LanguageDesc{ spellItr.second.SpellId, uint32(skillPair.second->SkillLine) });
+
+ for (LanguageDesc const& langDesc : langsWithSkill)
{
- auto iter = _langsMap.find(languageId);
- if (iter == _langsMap.cend())
- TC_LOG_WARN("server.loading", "Spell %u has language %u, which doesn't exist in Languages.db2", ability->Spell, languageId);
- else
- {
- iter->second.SpellId = ability->Spell;
- iter->second.SkillId = skillLineEntry->ID;
- ++count;
- }
+ // erase temporary assignment that lacked skill
+ Trinity::Containers::MultimapErasePair(_langsMap, langEntry->ID, { langDesc.SpellId, 0 });
+ _langsMap.emplace(langEntry->ID, langDesc);
}
}
}
- // Languages that don't have skills will be added in SpellMgr::LoadSpellInfoStore() (e.g. LANG_ZOMBIE, LANG_SHATH_YAR)
+ // Add the languages used in code in case they don't exist
+ _langsMap.emplace(LANG_UNIVERSAL, LanguageDesc());
+ _langsMap.emplace(LANG_ADDON, LanguageDesc());
+ _langsMap.emplace(LANG_ADDON_LOGGED, LanguageDesc());
// Log load time
- TC_LOG_INFO("server.loading", ">> Loaded %u languages skills in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
+ TC_LOG_INFO("server.loading", ">> Loaded %u languages in %u ms", uint32(_langsMap.size()), GetMSTimeDiffToNow(oldMSTime));
}
void LanguageMgr::LoadLanguagesWords()
@@ -146,8 +93,7 @@ void LanguageMgr::LoadLanguagesWords()
WordKey key = WordKey(wordEntry->LanguageID, length);
- auto result = _wordsMap.insert(std::make_pair(key, WordList()));
- result.first->second.push_back(wordEntry->Word);
+ _wordsMap[key].push_back(wordEntry->Word);
++wordsNum;
}
@@ -157,10 +103,7 @@ void LanguageMgr::LoadLanguagesWords()
LanguageMgr::WordList const* LanguageMgr::FindWordGroup(uint32 language, uint32 wordLen) const
{
- WordsMap::const_iterator iter = _wordsMap.find(WordKey(language, wordLen));
- if (iter != _wordsMap.end())
- return &(iter->second);
- return nullptr;
+ return Trinity::Containers::MapGetValuePtr(_wordsMap, WordKey(language, wordLen));
}
std::string LanguageMgr::Translate(std::string const& msg, uint32 sourcePlayerLanguage) const
@@ -221,14 +164,10 @@ uint32 LanguageMgr::SStrHash(char const* string, bool caseInsensitive, uint32 se
bool LanguageMgr::IsLanguageExist(uint32 languageId) const
{
- auto iter = _langsMap.find(languageId);
- return iter != _langsMap.cend();
+ return sLanguagesStore.HasRecord(languageId);
}
-LanguageDesc const* LanguageMgr::GetLanguageDescById(uint32 languageId) const
+Trinity::IteratorPair<LanguageMgr::LanguagesMap::const_iterator> LanguageMgr::GetLanguageDescById(Language languageId) const
{
- auto iter = _langsMap.find(languageId);
- if (iter == _langsMap.cend())
- return nullptr;
- return &(iter->second);
+ return Trinity::Containers::MapEqualRange(_langsMap, languageId);
}
diff --git a/src/server/game/Chat/LanguageMgr.h b/src/server/game/Chat/LanguageMgr.h
index 57e5a6428eb..26010bfbcf8 100644
--- a/src/server/game/Chat/LanguageMgr.h
+++ b/src/server/game/Chat/LanguageMgr.h
@@ -20,6 +20,7 @@
#include "Define.h"
#include "Hash.h"
+#include "IteratorPair.h"
#include "SharedDefines.h"
#include <string>
#include <vector>
@@ -27,11 +28,15 @@
struct LanguageDesc
{
- uint32 SpellId;
- uint32 SkillId;
+ uint32 SpellId = 0;
+ uint32 SkillId = 0;
+
+ friend bool operator==(LanguageDesc const& left, LanguageDesc const& right)
+ {
+ return left.SpellId == right.SpellId && left.SkillId == right.SkillId;
+ }
};
-struct SkillLineEntry;
struct SpellEffectEntry;
class TC_GAME_API LanguageMgr
@@ -43,7 +48,7 @@ class TC_GAME_API LanguageMgr
typedef std::vector<char const*> WordList;
typedef std::unordered_map<WordKey, WordList> WordsMap;
- typedef std::unordered_map<uint32, LanguageDesc> LanguagesMap;
+ typedef std::unordered_multimap<uint32, LanguageDesc> LanguagesMap;
// Constructors
private:
@@ -60,7 +65,7 @@ class TC_GAME_API LanguageMgr
uint32 SStrHash(char const* string, bool caseInsensitive, uint32 seed = 0x7FED7FED) const;
bool IsLanguageExist(uint32 languageId) const;
- LanguageDesc const* GetLanguageDescById(uint32 languageId) const;
+ Trinity::IteratorPair<LanguagesMap::const_iterator> GetLanguageDescById(Language languageId) const;
/* Calls a callback for each available language.
* Callback signature: bool callback(uint32 lang, LanguageDesc const& languageDesc)
@@ -83,11 +88,6 @@ class TC_GAME_API LanguageMgr
void LoadLanguagesWords();
void LoadLanguages();
- void LoadLanguagesSkills();
-
- bool IsRelevantLanguageSkill(SkillLineEntry const* skillLineEntry) const;
-
- uint32 GetSpellLanguage(uint32 spellId) const;
WordList const* FindWordGroup(uint32 language, uint32 wordLen) const;
diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp
index bdd97316759..3580a6903ec 100644
--- a/src/server/game/Conditions/ConditionMgr.cpp
+++ b/src/server/game/Conditions/ConditionMgr.cpp
@@ -2625,18 +2625,20 @@ bool ConditionMgr::IsPlayerMeetingCondition(Player const* player, PlayerConditio
if (condition->LanguageID)
{
- if (LanguageDesc const* langDesc = sLanguageMgr->GetLanguageDescById(condition->LanguageID))
+ int32 languageSkill = 0;
+ if (player->HasAuraTypeWithMiscvalue(SPELL_AURA_COMPREHEND_LANGUAGE, condition->LanguageID))
+ languageSkill = 300;
+ else
{
- int32 languageSkill = player->GetSkillValue(langDesc->SkillId);
- if (!languageSkill && player->HasAuraTypeWithMiscvalue(SPELL_AURA_COMPREHEND_LANGUAGE, condition->LanguageID))
- languageSkill = 300;
+ for (std::pair<uint32 const, LanguageDesc> const& languageDesc : sLanguageMgr->GetLanguageDescById(Language(condition->LanguageID)))
+ languageSkill = std::max<int32>(languageSkill, player->GetSkillValue(languageDesc.second.SkillId));
+ }
- if (condition->MinLanguage && languageSkill < condition->MinLanguage)
- return false;
+ if (condition->MinLanguage && languageSkill < condition->MinLanguage)
+ return false;
- if (condition->MaxLanguage && languageSkill > condition->MaxLanguage)
- return false;
- }
+ if (condition->MaxLanguage && languageSkill > condition->MaxLanguage)
+ return false;
}
if (condition->MinFactionID[0] && condition->MinFactionID[1] && condition->MinFactionID[2] && condition->MaxFactionID)
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index de9f0c1e0df..81e848bb87d 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -2782,15 +2782,6 @@ std::vector<SkillLineAbilityEntry const*> const* DB2Manager::GetSkillLineAbiliti
return Trinity::Containers::MapGetValuePtr(_skillLineAbilitiesBySkillupSkill, skillId);
}
-SkillRaceClassInfoEntry const* DB2Manager::GetAvailableSkillRaceClassInfo(uint32 skill) const
-{
- auto bounds = _skillRaceClassInfoBySkill.equal_range(skill);
- for (auto itr = bounds.first; itr != bounds.second; ++itr)
- if (itr->second->Availability == 1)
- return itr->second;
- return nullptr;
-}
-
SkillRaceClassInfoEntry const* DB2Manager::GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_)
{
auto bounds = _skillRaceClassInfoBySkill.equal_range(skill);
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index e442d5c0297..47ff813c03d 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -404,7 +404,6 @@ public:
std::vector<SkillLineEntry const*> const* GetSkillLinesForParentSkill(uint32 parentSkillId) const;
std::vector<SkillLineAbilityEntry const*> const* GetSkillLineAbilitiesBySkill(uint32 skillId) const;
SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_);
- SkillRaceClassInfoEntry const* GetAvailableSkillRaceClassInfo(uint32 skill) const;
std::vector<SpecializationSpellsEntry const*> const* GetSpecializationSpells(uint32 specId) const;
bool IsSpecSetMember(int32 specSetId, uint32 specId) const;
static bool IsValidSpellFamiliyName(SpellFamilyNames family);
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 208abfda260..f2ceb6bfd1a 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -22082,9 +22082,16 @@ void Player::Whisper(uint32 textId, Player* target, bool /*isBossWhisper = false
target->SendDirectMessage(packet.Write());
}
-bool Player::CanUnderstandLanguageSkillId(uint32 langSkillId) const
+bool Player::CanUnderstandLanguage(Language language) const
{
- return IsGameMaster() || (langSkillId && HasSkill(langSkillId));
+ if (IsGameMaster())
+ return true;
+
+ for (std::pair<uint32 const, LanguageDesc> const& languageDesc : sLanguageMgr->GetLanguageDescById(language))
+ if (languageDesc.second.SkillId && HasSkill(languageDesc.second.SkillId))
+ return true;
+
+ return false;
}
Item* Player::GetMItem(ObjectGuid::LowType id)
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index b664a7d6b62..335c60074a6 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1166,7 +1166,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void Whisper(uint32 textId, Player* target, bool isBossWhisper = false) override;
void WhisperAddon(std::string const& text, std::string const& prefix, bool isLogged, Player* receiver);
- bool CanUnderstandLanguageSkillId(uint32 langSkillId) const;
+ bool CanUnderstandLanguage(Language language) const;
/*********************************************************/
/*** STORAGE SYSTEM ***/
diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp
index 97ce6ab32a5..8f45920a5af 100644
--- a/src/server/game/Handlers/ChatHandler.cpp
+++ b/src/server/game/Handlers/ChatHandler.cpp
@@ -107,27 +107,18 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms
}
// prevent talking at unknown language (cheating)
- LanguageDesc const* langDesc = sLanguageMgr->GetLanguageDescById(lang);
- if (!langDesc)
+ auto languageData = sLanguageMgr->GetLanguageDescById(lang);
+ if (languageData.begin() == languageData.end())
{
SendNotification(LANG_UNKNOWN_LANGUAGE);
return;
}
- if (langDesc->SkillId != 0 && !sender->HasSkill(langDesc->SkillId))
+ if (std::none_of(languageData.begin(), languageData.end(),
+ [sender](std::pair<uint32 const, LanguageDesc> const& langDesc) { return langDesc.second.SkillId == 0 || sender->HasSkill(langDesc.second.SkillId); }))
{
// also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language)
- Unit::AuraEffectList const& langAuras = sender->GetAuraEffectsByType(SPELL_AURA_COMPREHEND_LANGUAGE);
- bool foundAura = false;
- for (Unit::AuraEffectList::const_iterator i = langAuras.begin(); i != langAuras.end(); ++i)
- {
- if ((*i)->GetMiscValue() == int32(lang))
- {
- foundAura = true;
- break;
- }
- }
- if (!foundAura)
+ if (!sender->HasAuraTypeWithMiscvalue(SPELL_AURA_COMPREHEND_LANGUAGE, lang))
{
SendNotification(LANG_NOT_LEARNED_LANGUAGE);
return;
diff --git a/src/server/game/Texts/ChatTextBuilder.cpp b/src/server/game/Texts/ChatTextBuilder.cpp
index b31d126d676..ea564c2aa7f 100644
--- a/src/server/game/Texts/ChatTextBuilder.cpp
+++ b/src/server/game/Texts/ChatTextBuilder.cpp
@@ -27,17 +27,15 @@ namespace Trinity
{
ChatPacketSender::ChatPacketSender(ChatMsg chatType, ::Language language, WorldObject const* sender, WorldObject const* receiver,
std::string message, uint32 achievementId /*= 0*/, LocaleConstant locale /*= LOCALE_enUS*/)
- : Type(chatType), Language(language), Sender(sender), Receiver(receiver), Text(std::move(message)), AchievementId(achievementId), Locale(locale), LanguageSkillId(0)
+ : Type(chatType), Language(language), Sender(sender), Receiver(receiver), Text(std::move(message)), AchievementId(achievementId), Locale(locale)
{
UntranslatedPacket.Initialize(Type, Language, Sender, Receiver, Text, AchievementId, "", Locale);
UntranslatedPacket.Write();
- if (LanguageDesc const* languageDesc = sLanguageMgr->GetLanguageDescById(language))
- LanguageSkillId = languageDesc->SkillId;
}
void ChatPacketSender::operator()(Player const* player) const
{
- if (Language == LANG_UNIVERSAL || Language == LANG_ADDON || Language == LANG_ADDON_LOGGED || player->CanUnderstandLanguageSkillId(LanguageSkillId))
+ if (Language == LANG_UNIVERSAL || Language == LANG_ADDON || Language == LANG_ADDON_LOGGED || player->CanUnderstandLanguage(Language))
{
player->SendDirectMessage(UntranslatedPacket.GetRawPacket());
return;
diff --git a/src/server/game/Texts/ChatTextBuilder.h b/src/server/game/Texts/ChatTextBuilder.h
index 74d677dfbfa..94ae1339652 100644
--- a/src/server/game/Texts/ChatTextBuilder.h
+++ b/src/server/game/Texts/ChatTextBuilder.h
@@ -40,8 +40,6 @@ namespace Trinity
uint32 AchievementId;
LocaleConstant Locale;
- uint32 LanguageSkillId;
-
public:
// caches
WorldPackets::Chat::Chat UntranslatedPacket;
diff --git a/src/server/game/Texts/CreatureTextMgr.cpp b/src/server/game/Texts/CreatureTextMgr.cpp
index 430af3e1a55..3f2cdb660eb 100644
--- a/src/server/game/Texts/CreatureTextMgr.cpp
+++ b/src/server/game/Texts/CreatureTextMgr.cpp
@@ -82,7 +82,7 @@ void CreatureTextMgr::LoadCreatureTexts()
}
}
- if (!sLanguageMgr->IsLanguageExist(temp.lang))
+ if (temp.lang != LANG_UNIVERSAL && !sLanguageMgr->IsLanguageExist(temp.lang))
{
TC_LOG_ERROR("sql.sql", "CreatureTextMgr: Entry %u, Group %u in table `creature_text` using Language %u but Language does not exist.", temp.creatureId, temp.groupId, uint32(temp.lang));
temp.lang = LANG_UNIVERSAL;
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index bbcfce3ed97..a2fdd5aea7d 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1756,12 +1756,6 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Initializing PlayerDump tables...");
PlayerDump::InitializeTables();
- TC_LOG_INFO("server.loading", "Loading languages...");
- sLanguageMgr->LoadLanguages();
-
- TC_LOG_INFO("server.loading", "Loading languages words...");
- sLanguageMgr->LoadLanguagesWords();
-
TC_LOG_INFO("server.loading", "Loading SpellInfo store...");
sSpellMgr->LoadSpellInfoStore();
@@ -1783,15 +1777,18 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading SpellInfo immunity infos...");
sSpellMgr->LoadSpellInfoImmunities();
- TC_LOG_INFO("server.loading", "Loading languages skills...");
- sLanguageMgr->LoadLanguagesSkills();
-
TC_LOG_INFO("server.loading", "Loading PetFamilySpellsStore Data...");
sSpellMgr->LoadPetFamilySpellsStore();
TC_LOG_INFO("server.loading", "Loading Spell Totem models...");
sSpellMgr->LoadSpellTotemModel();
+ TC_LOG_INFO("server.loading", "Loading languages..."); // must be after LoadSpellInfoStore and LoadSkillLineAbilityMap
+ sLanguageMgr->LoadLanguages();
+
+ TC_LOG_INFO("server.loading", "Loading languages words...");
+ sLanguageMgr->LoadLanguagesWords();
+
TC_LOG_INFO("server.loading", "Loading GameObject models...");
LoadGameObjectModelList(m_dataPath);
diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp
index b67c1427f63..4b86ff544cf 100644
--- a/src/server/scripts/Commands/cs_learn.cpp
+++ b/src/server/scripts/Commands/cs_learn.cpp
@@ -291,7 +291,9 @@ public:
{
sLanguageMgr->ForEachLanguage([handler](uint32 /*lang*/, LanguageDesc const& languageDesc)
{
- handler->GetSession()->GetPlayer()->LearnSpell(languageDesc.SpellId, false);
+ if (languageDesc.SpellId)
+ handler->GetSession()->GetPlayer()->LearnSpell(languageDesc.SpellId, false);
+
return true;
});