From cbb22d291e531a3e337b68a667966ab79613246d Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 3 May 2021 22:15:02 -0400 Subject: [PATCH] Core/Chat: Allow incorrect spell/skill language assignments to mirror client behavior Port From (https://github.com/TrinityCore/TrinityCore/commit/14098b28b39bc9d1ea17d18a7ecd3dd610f29cdc) --- Source/Game/Chat/Commands/LearnCommands.cs | 4 +- Source/Game/Chat/LanguageManager.cs | 100 +++++---------------- Source/Game/Conditions/ConditionManager.cs | 23 ++--- Source/Game/DataStorage/DB2Manager.cs | 10 --- Source/Game/Entities/Player/Player.cs | 11 ++- Source/Game/Globals/ObjectManager.cs | 30 +++++++ Source/Game/Handlers/ChatHandler.cs | 18 +--- Source/Game/Server/WorldManager.cs | 15 ++-- Source/Game/Text/ChatTextBuilder.cs | 8 +- Source/Game/Text/CreatureTextManager.cs | 2 +- 10 files changed, 89 insertions(+), 132 deletions(-) diff --git a/Source/Game/Chat/Commands/LearnCommands.cs b/Source/Game/Chat/Commands/LearnCommands.cs index ff2cd5425..d3f9ce785 100644 --- a/Source/Game/Chat/Commands/LearnCommands.cs +++ b/Source/Game/Chat/Commands/LearnCommands.cs @@ -97,7 +97,9 @@ namespace Game.Chat.Commands // skipping UNIVERSAL language (0) Global.LanguageMgr.ForEachLanguage((lang, languageDesc) => { - handler.GetSession().GetPlayer().LearnSpell(languageDesc.SpellId, false); + if (languageDesc.SpellId != 0) + handler.GetSession().GetPlayer().LearnSpell(languageDesc.SpellId, false); + return true; }); diff --git a/Source/Game/Chat/LanguageManager.cs b/Source/Game/Chat/LanguageManager.cs index 8229579e3..54f57819c 100644 --- a/Source/Game/Chat/LanguageManager.cs +++ b/Source/Game/Chat/LanguageManager.cs @@ -31,7 +31,7 @@ namespace Game.Chat { public class LanguageManager : Singleton { - Dictionary _langsMap = new(); + MultiMap _langsMap = new(); MultiMap, string> _wordsMap = new(); LanguageManager() { } @@ -41,36 +41,7 @@ namespace Game.Chat Cypher.Assert(spellEffect != null && spellEffect.Effect == (uint)SpellEffectName.Language); uint languageId = (uint)spellEffect.EffectMiscValue[0]; - if (!_langsMap.TryGetValue(languageId, out LanguageDesc desc)) - { - Log.outWarn(LogFilter.Spells, $"LoadSpellEffectLanguage called on Spell {spellEffect.SpellID} with language {languageId} which does not exist in Language.db2!"); - return; - } - - desc.SpellId = spellEffect.SpellID; - } - - uint GetSpellLanguage(uint spellId) - { - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, Difficulty.None); - if (spellInfo != null) - { - var effects = spellInfo.GetEffects(); - if (effects.Length != 1 || effects[0].Effect != SpellEffectName.Language) - Log.outWarn(LogFilter.Spells, $"Invalid language spell {spellId}. Expected 1 effect with SPELL_EFFECT_LANGUAGE"); - else - return (uint)effects[0].MiscValue; - } - return 0; - } - - bool IsRelevantLanguageSkill(SkillLineRecord skillLineEntry) - { - if (skillLineEntry == null) - return false; - - SkillRaceClassInfoRecord entry = Global.DB2Mgr.GetAvailableSkillRaceClassInfo(skillLineEntry.Id); - return entry != null; + _langsMap.Add(languageId, new LanguageDesc(spellEffect.SpellID, 0)); // register without a skill id for now } public void LoadLanguages() @@ -79,7 +50,25 @@ namespace Game.Chat // Load languages from Languages.db2. Just the id, we don't need the name foreach (LanguagesRecord langEntry in CliDB.LanguagesStorage.Values) - _langsMap.Add(langEntry.Id, new LanguageDesc()); + { + var spellsRange = _langsMap.LookupByKey(langEntry.Id); + if (spellsRange.Empty()) + _langsMap.Add(langEntry.Id, new LanguageDesc()); + else + { + List langsWithSkill = new(); + foreach (var spellItr in spellsRange) + foreach (var skillPair in Global.SpellMgr.GetSkillLineAbilityMapBounds(spellItr.SpellId)) + langsWithSkill.Add(new LanguageDesc(spellItr.SpellId, (uint)skillPair.SkillLine)); + + foreach (var langDesc in langsWithSkill) + { + // erase temporary assignment that lacked skill + _langsMap.Remove(langEntry.Id, new LanguageDesc(langDesc.SpellId, 0)); + _langsMap.Add(langEntry.Id, langDesc); + } + } + } // Add the languages used in code in case they don't exist _langsMap.Add((uint)Language.Universal, new LanguageDesc()); @@ -90,49 +79,6 @@ namespace Game.Chat Log.outInfo(LogFilter.ServerLoading, $"Loaded {_langsMap.Count} languages in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } - public void LoadLanguagesSkills() - { - uint oldMSTime = Time.GetMSTime(); - - uint count = 0; - foreach (SkillLineRecord skillLineEntry in CliDB.SkillLineStorage.Values) - { - if (skillLineEntry.CategoryID != SkillCategory.Languages) - continue; - - if (!IsRelevantLanguageSkill(skillLineEntry)) - continue; - - var skills = Global.DB2Mgr.GetSkillLineAbilitiesBySkill(skillLineEntry.Id); - - // We're expecting only 1 skill - if (skills.Count != 1) - Log.outWarn(LogFilter.ServerLoading, $"Found language skill line with {skills.Count} spells. Expected 1. Will use 1st if available"); - - SkillLineAbilityRecord ability = skills.Empty() ? null : skills[0]; - if (ability != null) - { - uint languageId = GetSpellLanguage(ability.Spell); - if (languageId != 0) - { - if (!_langsMap.TryGetValue(languageId, out LanguageDesc desc)) - Log.outWarn(LogFilter.ServerLoading, $"Spell {ability.Spell} has language {languageId}, which doesn't exist in Languages.db2"); - else - { - desc.SpellId = ability.Spell; - desc.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 - Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} languages skills in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); - } - public void LoadLanguagesWords() { uint oldMSTime = Time.GetMSTime(); @@ -215,10 +161,10 @@ namespace Game.Chat public bool IsLanguageExist(Language languageId) { - return _langsMap.ContainsKey((uint)languageId); + return CliDB.LanguagesStorage.HasRecord((uint)languageId); } - public LanguageDesc GetLanguageDescById(Language languageId) + public List GetLanguageDescById(Language languageId) { return _langsMap.LookupByKey((uint)languageId); } diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index 61b9eafc8..d1d950013 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -1807,19 +1807,20 @@ namespace Game if (condition.LanguageID != 0) { - LanguageDesc langDesc = Global.LanguageMgr.GetLanguageDescById((Language)condition.LanguageID); - if (langDesc != null) + int languageSkill = 0; + if (player.HasAuraTypeWithMiscvalue(AuraType.ComprehendLanguage, condition.LanguageID)) + languageSkill = 300; + else { - uint languageSkill = player.GetSkillValue((SkillType)langDesc.SkillId); - if (languageSkill == 0 && player.HasAuraTypeWithMiscvalue(AuraType.ComprehendLanguage, condition.LanguageID)) - languageSkill = 300; - - if (condition.MinLanguage != 0 && languageSkill < condition.MinLanguage) - return false; - - if (condition.MaxLanguage != 0 && languageSkill > condition.MaxLanguage) - return false; + foreach (var languageDesc in Global.LanguageMgr.GetLanguageDescById((Language)condition.LanguageID)) + languageSkill = Math.Max(languageSkill, player.GetSkillValue((SkillType)languageDesc.SkillId)); } + + if (condition.MinLanguage != 0 && languageSkill < condition.MinLanguage) + return false; + + if (condition.MaxLanguage != 0 && languageSkill > condition.MaxLanguage) + return false; } if (condition.MinFactionID[0] != 0 && condition.MinFactionID[1] != 0 && condition.MinFactionID[2] != 0 && condition.MaxFactionID != 0) diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 820660661..1be3d789d 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -1850,16 +1850,6 @@ namespace Game.DataStorage { return _skillLineAbilitiesBySkillupSkill.LookupByKey(skillId); } - - public SkillRaceClassInfoRecord GetAvailableSkillRaceClassInfo(uint skill) - { - var bounds = _skillRaceClassInfoBySkill.LookupByKey(skill); - foreach (var record in bounds) - if (record.Availability == 1) - return record; - - return null; - } public SkillRaceClassInfoRecord GetSkillRaceClassInfo(uint skill, Race race, Class class_) { diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 4bc188e24..4d491ee69 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -6334,9 +6334,16 @@ namespace Game.Entities packet.Initialize(ChatMsg.Whisper, Language.Universal, this, target, Global.DB2Mgr.GetBroadcastTextValue(bct, locale, GetGender())); target.SendPacket(packet); } - public bool CanUnderstandLanguageSkillId(uint langSkillId) + public bool CanUnderstandLanguage(Language language) { - return IsGameMaster() || (langSkillId != 0 && HasSkill((SkillType)langSkillId)); + if (IsGameMaster()) + return true; + + foreach (var languageDesc in Global.LanguageMgr.GetLanguageDescById(language)) + if (languageDesc.SkillId != 0 && HasSkill((SkillType)languageDesc.SkillId)) + return true; + + return false; } #endregion diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 8d333959a..503ec2806 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -10887,6 +10887,36 @@ namespace Game { public uint SpellId; public uint SkillId; + + public LanguageDesc() { } + public LanguageDesc(uint spellId, uint skillId) + { + SpellId = spellId; + SkillId = skillId; + } + + public override int GetHashCode() + { + return SpellId.GetHashCode() ^ SkillId.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj is LanguageDesc) + return (LanguageDesc)obj == this; + + return false; + } + + public static bool operator ==(LanguageDesc left, LanguageDesc right) + { + return left.SpellId == right.SpellId && left.SkillId == right.SkillId; + } + + public static bool operator !=(LanguageDesc left, LanguageDesc right) + { + return !(left == right); + } } class ItemSpecStats diff --git a/Source/Game/Handlers/ChatHandler.cs b/Source/Game/Handlers/ChatHandler.cs index d6b07a74e..f0a9503a0 100644 --- a/Source/Game/Handlers/ChatHandler.cs +++ b/Source/Game/Handlers/ChatHandler.cs @@ -106,27 +106,17 @@ namespace Game } // prevent talking at unknown language (cheating) - LanguageDesc langDesc = Global.LanguageMgr.GetLanguageDescById(lang); - if (langDesc == null) + var languageData = Global.LanguageMgr.GetLanguageDescById(lang); + if (languageData.Empty()) { SendNotification(CypherStrings.UnknownLanguage); return; } - if (langDesc.SkillId != 0 && !sender.HasSkill((SkillType)langDesc.SkillId)) + if (!languageData.Any(langDesc => langDesc.SkillId == 0 || sender.HasSkill((SkillType)langDesc.SkillId))) { // also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language) - var langAuras = sender.GetAuraEffectsByType(AuraType.ComprehendLanguage); - bool foundAura = false; - foreach (var eff in langAuras) - { - if (eff.GetMiscValue() == (int)lang) - { - foundAura = true; - break; - } - } - if (!foundAura) + if (!sender.HasAuraTypeWithMiscvalue(AuraType.ComprehendLanguage, (int)lang)) { SendNotification(CypherStrings.NotLearnedLanguage); return; diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index 9002d7411..6b2b31933 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -439,12 +439,6 @@ namespace Game Global.VMapMgr.Initialize(mapData); Global.MMapMgr.Initialize(mapData); - Log.outInfo(LogFilter.ServerLoading, "Loading languages..."); - Global.LanguageMgr.LoadLanguages(); - - Log.outInfo(LogFilter.ServerLoading, "Loading languages words..."); - Global.LanguageMgr.LoadLanguagesWords(); - Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo Storage..."); Global.SpellMgr.LoadSpellInfoStore(); @@ -466,15 +460,18 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading SpellInfo immunity infos..."); Global.SpellMgr.LoadSpellInfoImmunities(); - Log.outInfo(LogFilter.ServerLoading, "Loading languages skills..."); - Global.LanguageMgr.LoadLanguagesSkills(); - Log.outInfo(LogFilter.ServerLoading, "Loading PetFamilySpellsStore Data..."); Global.SpellMgr.LoadPetFamilySpellsStore(); Log.outInfo(LogFilter.ServerLoading, "Loading Spell Totem models..."); Global.SpellMgr.LoadSpellTotemModel(); + Log.outInfo(LogFilter.ServerLoading, "Loading languages..."); + Global.LanguageMgr.LoadLanguages(); + + Log.outInfo(LogFilter.ServerLoading, "Loading languages words..."); + Global.LanguageMgr.LoadLanguagesWords(); + Log.outInfo(LogFilter.ServerLoading, "Loading GameObject models..."); GameObjectModel.LoadGameObjectModelList(); diff --git a/Source/Game/Text/ChatTextBuilder.cs b/Source/Game/Text/ChatTextBuilder.cs index 00d8b4884..8a4d913fe 100644 --- a/Source/Game/Text/ChatTextBuilder.cs +++ b/Source/Game/Text/ChatTextBuilder.cs @@ -40,8 +40,6 @@ namespace Game.Chat uint AchievementId; Locale Locale; - uint LanguageSkillId; - // caches public ChatPkt UntranslatedPacket; public Optional TranslatedPacket; @@ -58,15 +56,11 @@ namespace Game.Chat UntranslatedPacket.Initialize(Type, Language, Sender, Receiver, Text, AchievementId, "", Locale); UntranslatedPacket.Write(); - - LanguageDesc languageDesc = Global.LanguageMgr.GetLanguageDescById(language); - if (languageDesc != null) - LanguageSkillId = languageDesc.SkillId; } public void Invoke(Player player) { - if (Language == Language.Universal || Language == Language.Addon || Language == Language.AddonLogged || player.CanUnderstandLanguageSkillId(LanguageSkillId)) + if (Language == Language.Universal || Language == Language.Addon || Language == Language.AddonLogged || player.CanUnderstandLanguage(Language)) { player.SendPacket(UntranslatedPacket); return; diff --git a/Source/Game/Text/CreatureTextManager.cs b/Source/Game/Text/CreatureTextManager.cs index 9c015483b..5a23a2545 100644 --- a/Source/Game/Text/CreatureTextManager.cs +++ b/Source/Game/Text/CreatureTextManager.cs @@ -78,7 +78,7 @@ namespace Game temp.sound = 0; } } - if (!Global.LanguageMgr.IsLanguageExist(temp.lang)) + if (temp.lang != Language.Universal && !Global.LanguageMgr.IsLanguageExist(temp.lang)) { Log.outError(LogFilter.Sql, "GossipManager: Entry {0}, Group {1} in table `creature_texts` using Language {2} but Language does not exist.", temp.creatureId, temp.groupId, temp.lang); temp.lang = Language.Universal;