using System.Linq; using Content.Shared._White.RemoteControl; using Content.Shared.Language; using Content.Shared.Language.Components; using Content.Shared.Language.Events; using Content.Shared.Language.Systems; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; namespace Content.Server.Language; public sealed partial class LanguageSystem : SharedLanguageSystem { public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInitLanguageSpeaker); SubscribeLocalEvent(OnGetLanguageState); SubscribeLocalEvent(OnDetermineUniversalLanguages); SubscribeNetworkEvent(OnClientSetLanguage); SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); } #region event handling private void OnInitLanguageSpeaker(Entity ent, ref MapInitEvent args) { if (string.IsNullOrEmpty(ent.Comp.CurrentLanguage)) ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); UpdateEntityLanguages(ent!); } private void OnGetLanguageState(Entity entity, ref ComponentGetState args) { args.State = new LanguageSpeakerComponent.State { CurrentLanguage = entity.Comp.CurrentLanguage, SpokenLanguages = entity.Comp.SpokenLanguages, UnderstoodLanguages = entity.Comp.UnderstoodLanguages }; } private void OnDetermineUniversalLanguages(Entity entity, ref DetermineEntityLanguagesEvent ev) { // We only add it as a spoken language; CanUnderstand checks for ULSC itself. if (entity.Comp.Enabled) ev.SpokenLanguages.Add(PsychomanticPrototype); } private void OnClientSetLanguage(LanguagesSetMessage message, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not { Valid: true } uid) return; var language = GetLanguagePrototype(message.CurrentLanguage); if (language == null || !CanSpeak(uid, language.ID)) return; SetLanguage(uid, language.ID); } #endregion #region public api public bool CanUnderstand(Entity ent, ProtoId language) { if (language == PsychomanticPrototype || language == UniversalPrototype || TryComp(ent, out var uni) && uni.Enabled) return true; // WWDP EDIT START // quick fix // todo: reimplement as an event handler on RemoteControllableComponent. if(TryComp(ent.Owner, out var controllable) && controllable.ControllingEntity is EntityUid controller) return CanUnderstand(controller, language); // WWDP EDIT END return Resolve(ent, ref ent.Comp, logMissing: false) && ent.Comp.UnderstoodLanguages.Contains(language); } public bool CanSpeak(Entity ent, ProtoId language) { if (!Resolve(ent, ref ent.Comp, logMissing: false)) return false; // WWDP EDIT START // quick fix // todo: reimplement as an event handler on RemoteControllableComponent. if (TryComp(ent.Owner, out var controllable) && controllable.ControllingEntity is EntityUid controller) return CanSpeak(controller, language); // WWDP EDIT END return ent.Comp.SpokenLanguages.Contains(language); } /// /// Returns the current language of the given entity, assumes Universal if it's not a language speaker. /// public LanguagePrototype GetLanguage(Entity ent) { if (!Resolve(ent, ref ent.Comp, logMissing: false) || string.IsNullOrEmpty(ent.Comp.CurrentLanguage) || !_prototype.TryIndex(ent.Comp.CurrentLanguage, out var proto) ) return Universal; return proto; } /// /// Returns the list of languages this entity can speak. /// /// This simply returns the value of . public List> GetSpokenLanguages(EntityUid uid) { return TryComp(uid, out var component) ? component.SpokenLanguages : []; } /// /// Returns the list of languages this entity can understand. /// This simply returns the value of . public List> GetUnderstoodLanguages(EntityUid uid) { return TryComp(uid, out var component) ? component.UnderstoodLanguages : []; } public void SetLanguage(Entity ent, ProtoId language) { if (!CanSpeak(ent, language) || !Resolve(ent, ref ent.Comp) || ent.Comp.CurrentLanguage == language) return; ent.Comp.CurrentLanguage = language; RaiseLocalEvent(ent, new LanguagesUpdateEvent(), true); Dirty(ent); } /// /// Adds a new language to the respective lists of intrinsically known languages of the given entity. /// public void AddLanguage( EntityUid uid, ProtoId language, bool addSpoken = true, bool addUnderstood = true) { EnsureComp(uid, out var knowledge); EnsureComp(uid, out var speaker); 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( Entity ent, ProtoId language, bool removeSpoken = true, bool removeUnderstood = true) { if (!Resolve(ent, ref ent.Comp, false)) return; if (removeSpoken) ent.Comp.SpokenLanguages.Remove(language); if (removeUnderstood) ent.Comp.UnderstoodLanguages.Remove(language); // We don't ensure that the entity has a speaker comp. If it doesn't... Well, woe be the caller of this method. UpdateEntityLanguages(ent.Owner); } /// /// 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(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return false; if (!ent.Comp.SpokenLanguages.Contains(ent.Comp.CurrentLanguage)) { ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); RaiseLocalEvent(ent, new LanguagesUpdateEvent()); Dirty(ent); return true; } return false; } /// /// Immediately refreshes the cached lists of spoken and understood languages for the given entity. /// public void UpdateEntityLanguages(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return; var ev = new DetermineEntityLanguagesEvent(); // We add the intrinsically known languages first so other systems can manipulate them easily if (TryComp(ent, out var knowledge)) { foreach (var spoken in knowledge.SpokenLanguages) ev.SpokenLanguages.Add(spoken); foreach (var understood in knowledge.UnderstoodLanguages) ev.UnderstoodLanguages.Add(understood); } RaiseLocalEvent(ent, ref ev); ent.Comp.SpokenLanguages.Clear(); ent.Comp.UnderstoodLanguages.Clear(); ent.Comp.SpokenLanguages.AddRange(ev.SpokenLanguages); ent.Comp.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages); // If EnsureValidLanguage returns true, it also raises a LanguagesUpdateEvent, so we try to avoid raising it twice in that case. if (!EnsureValidLanguage(ent)) RaiseLocalEvent(ent, new LanguagesUpdateEvent()); Dirty(ent); } #endregion }