using System.Linq; using Content.Server.Language.Events; using Content.Shared.Language; using Content.Shared.Language.Components; using Content.Shared.Language.Events; using Content.Shared.Language.Systems; using UniversalLanguageSpeakerComponent = Content.Shared.Language.Components.UniversalLanguageSpeakerComponent; namespace Content.Server.Language; public sealed partial class LanguageSystem : SharedLanguageSystem { public override void Initialize() { base.Initialize(); InitializeNet(); SubscribeLocalEvent(OnInitLanguageSpeaker); } #region public api public bool CanUnderstand(EntityUid listener, string language, LanguageSpeakerComponent? component = null) { if (language == UniversalPrototype || HasComp(listener)) return true; if (!Resolve(listener, ref component, logMissing: false)) return false; return component.UnderstoodLanguages.Contains(language); } public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponent? component = null) { if (HasComp(speaker)) return true; if (!Resolve(speaker, ref component, logMissing: false)) return false; return component.SpokenLanguages.Contains(language); } /// /// Returns the current language of the given entity, assumes Universal if it's not a language speaker. /// public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? component = null) { if (HasComp(speaker) || !Resolve(speaker, ref component, logMissing: false)) return Universal; // Serves both as a fallback and uhhh something (TODO: fix this comment) if (string.IsNullOrEmpty(component.CurrentLanguage) || !_prototype.TryIndex(component.CurrentLanguage, out var proto)) return Universal; return proto; } /// /// Returns the list of languages this entity can speak. /// /// Typically, checking is sufficient. public List GetSpokenLanguages(EntityUid uid) { if (HasComp(uid)) return [UniversalPrototype]; if (TryComp(uid, out var component)) return component.SpokenLanguages; return []; } /// /// Returns the list of languages this entity can understand. /// /// Typically, checking is sufficient. public List GetUnderstoodLanguages(EntityUid uid) { if (HasComp(uid)) return [UniversalPrototype]; // This one is tricky because... well, they understand all of them, not just one. if (TryComp(uid, out var component)) return component.UnderstoodLanguages; return []; } public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? component = null) { if (!CanSpeak(speaker, language) || (HasComp(speaker) && language != UniversalPrototype)) return; if (!Resolve(speaker, ref component) || component.CurrentLanguage == language) return; component.CurrentLanguage = language; RaiseLocalEvent(speaker, new LanguagesUpdateEvent(), true); } /// /// Adds a new language to the respective lists of intrinsically known languages of the given entity. /// public void AddLanguage( EntityUid uid, string language, bool addSpoken = true, bool addUnderstood = true, LanguageKnowledgeComponent? knowledge = null, LanguageSpeakerComponent? speaker = null) { if (knowledge == null) knowledge = EnsureComp(uid); if (addSpoken && !knowledge.SpokenLanguages.Contains(language)) knowledge.SpokenLanguages.Add(language); if (addUnderstood && !knowledge.UnderstoodLanguages.Contains(language)) knowledge.UnderstoodLanguages.Add(language); UpdateEntityLanguages(uid, speaker); } /// /// Removes a language from the respective lists of intrinsically known languages of the given entity. /// public void RemoveLanguage( EntityUid uid, string language, bool removeSpoken = true, bool removeUnderstood = true, LanguageKnowledgeComponent? knowledge = null, LanguageSpeakerComponent? speaker = null) { if (knowledge == null) knowledge = EnsureComp(uid); if (removeSpoken) knowledge.SpokenLanguages.Remove(language); if (removeUnderstood) knowledge.UnderstoodLanguages.Remove(language); UpdateEntityLanguages(uid, speaker); } /// /// Ensures the given entity has a valid language as its current language. /// If not, sets it to the first entry of its SpokenLanguages list, or universal if it's empty. /// /// True if the current language was modified, false otherwise. public bool EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp = null) { if (!Resolve(entity, ref comp)) return false; if (!comp.SpokenLanguages.Contains(comp.CurrentLanguage)) { comp.CurrentLanguage = comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); RaiseLocalEvent(entity, new LanguagesUpdateEvent()); return true; } return false; } /// /// Immediately refreshes the cached lists of spoken and understood languages for the given entity. /// public void UpdateEntityLanguages(EntityUid entity, LanguageSpeakerComponent? languages = null) { if (!Resolve(entity, ref languages)) return; var ev = new DetermineEntityLanguagesEvent(); // We add the intrinsically known languages first so other systems can manipulate them easily if (TryComp(entity, out var knowledge)) { foreach (var spoken in knowledge.SpokenLanguages) ev.SpokenLanguages.Add(spoken); foreach (var understood in knowledge.UnderstoodLanguages) ev.UnderstoodLanguages.Add(understood); } RaiseLocalEvent(entity, ref ev); languages.SpokenLanguages.Clear(); languages.UnderstoodLanguages.Clear(); languages.SpokenLanguages.AddRange(ev.SpokenLanguages); languages.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages); if (!EnsureValidLanguage(entity)) RaiseLocalEvent(entity, new LanguagesUpdateEvent()); } #endregion #region event handling private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent component, ComponentInit args) { if (string.IsNullOrEmpty(component.CurrentLanguage)) component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype); UpdateEntityLanguages(uid, component); } #endregion }