mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Chat: Allow incorrect spell/skill language assignments to mirror client behavior
This commit is contained in:
@@ -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,7 +53,25 @@ 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());
|
||||
{
|
||||
auto spellsRange = Trinity::Containers::MapEqualRange(_langsMap, langEntry->ID);
|
||||
if (spellsRange.begin() == spellsRange.end())
|
||||
_langsMap.emplace(langEntry->ID, LanguageDesc());
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
// erase temporary assignment that lacked skill
|
||||
Trinity::Containers::MultimapErasePair(_langsMap, langEntry->ID, { langDesc.SpellId, 0 });
|
||||
_langsMap.emplace(langEntry->ID, langDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the languages used in code in case they don't exist
|
||||
_langsMap.emplace(LANG_UNIVERSAL, LanguageDesc());
|
||||
@@ -93,48 +82,6 @@ void LanguageMgr::LoadLanguages()
|
||||
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))
|
||||
{
|
||||
if (uint32 languageId = GetSpellLanguage(ability->Spell))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Languages that don't have skills will be added in SpellMgr::LoadSpellInfoStore() (e.g. LANG_ZOMBIE, LANG_SHATH_YAR)
|
||||
|
||||
// Log load time
|
||||
TC_LOG_INFO("server.loading", ">> Loaded %u languages skills in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void LanguageMgr::LoadLanguagesWords()
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
if (condition->MinLanguage && languageSkill < condition->MinLanguage)
|
||||
return false;
|
||||
|
||||
if (condition->MaxLanguage && languageSkill > condition->MaxLanguage)
|
||||
return false;
|
||||
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->MaxLanguage && languageSkill > condition->MaxLanguage)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (condition->MinFactionID[0] && condition->MinFactionID[1] && condition->MinFactionID[2] && condition->MaxFactionID)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ***/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,8 +40,6 @@ namespace Trinity
|
||||
uint32 AchievementId;
|
||||
LocaleConstant Locale;
|
||||
|
||||
uint32 LanguageSkillId;
|
||||
|
||||
public:
|
||||
// caches
|
||||
WorldPackets::Chat::Chat UntranslatedPacket;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user