Core/Chat: Allow incorrect spell/skill language assignments to mirror client behavior

This commit is contained in:
Shauren
2021-05-02 00:44:31 +02:00
parent ede3e415ab
commit 14098b28b3
13 changed files with 75 additions and 151 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)

View File

@@ -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 ***/

View File

@@ -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;

View File

@@ -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;

View File

@@ -40,8 +40,6 @@ namespace Trinity
uint32 AchievementId;
LocaleConstant Locale;
uint32 LanguageSkillId;
public:
// caches
WorldPackets::Chat::Chat UntranslatedPacket;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
});