Files
wwdpublic/Content.Server/Language/TranslatorSystem.cs
VMSolidus 19ca517509 Non-Destructive Universal Language (#899)
# Description

UniversalLanguageSpeakerComponent has NUMEROUS issues, which previously
were easy to ignore since it was a language only obtainable by Admins.
But now that it's both a Psionic Power(And a trait that adds said
power), the issues with it have been highlighted. Here's just a FEW

1. UniversalLanguageSpeaker overwrites all known languages, preventing
you from speaking anything else
2. It overwrites the ability to use sign language.
3. It also overwrites the Mute trait.

To fix that, I've made it follow *MOSTLY* the logic all the other
languages use, so that it's less of a special snowflake case. Now if you
have access to it, it will appear in your language list alongside other
languages, rather than fully replacing the entire list. That way you can
intentionally choose not to speak in a language understood by all.

Fuck it, I also added the ability for the TraitSystem to just call
LanguageSystem and directly add arbitrarily any desired language, rather
than needing a component to do so.

# Changelog

🆑
- fix: UniversalLanguageSpeaker(And Xenoglossy by extension) will now
appear in your language menu alongside other known languages, rather
than replace all known languages. You can effectively now choose whether
or not to speak it, or to use a normal language.
- add: Traits can now add Languages directly.
- add: Traits can now remove Languages directly.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com>
Co-authored-by: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com>
Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com>
2024-10-19 13:08:20 +07:00

165 lines
7.0 KiB
C#

using System.Linq;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Language;
using Content.Shared.Language.Components;
using Content.Shared.Language.Systems;
using Content.Shared.PowerCell;
using Content.Shared.Language.Components.Translators;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
namespace Content.Server.Language;
public sealed class TranslatorSystem : SharedTranslatorSystem
{
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly LanguageSystem _language = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<IntrinsicTranslatorComponent, DetermineEntityLanguagesEvent>(OnDetermineLanguages);
SubscribeLocalEvent<HoldsTranslatorComponent, DetermineEntityLanguagesEvent>(OnProxyDetermineLanguages);
SubscribeLocalEvent<HandheldTranslatorComponent, EntGotInsertedIntoContainerMessage>(OnTranslatorInserted);
SubscribeLocalEvent<HandheldTranslatorComponent, EntParentChangedMessage>(OnTranslatorParentChanged);
SubscribeLocalEvent<HandheldTranslatorComponent, ActivateInWorldEvent>(OnTranslatorToggle);
SubscribeLocalEvent<HandheldTranslatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
}
private void OnDetermineLanguages(EntityUid uid, IntrinsicTranslatorComponent component, DetermineEntityLanguagesEvent ev)
{
if (!component.Enabled
|| component.LifeStage >= ComponentLifeStage.Removing
|| !TryComp<LanguageKnowledgeComponent>(uid, out var knowledge)
|| !_powerCell.HasActivatableCharge(uid))
return;
CopyLanguages(component, ev, knowledge);
}
private void OnProxyDetermineLanguages(EntityUid uid, HoldsTranslatorComponent component, DetermineEntityLanguagesEvent ev)
{
if (!TryComp<LanguageKnowledgeComponent>(uid, out var knowledge))
return;
foreach (var (translator, translatorComp) in component.Translators.ToArray())
{
if (!translatorComp.Enabled || !_powerCell.HasActivatableCharge(uid))
continue;
if (!_containers.TryGetContainingContainer(translator, out var container) || container.Owner != uid)
{
component.Translators.RemoveWhere(it => it.Owner == translator);
continue;
}
CopyLanguages(translatorComp, ev, knowledge);
}
}
private void OnTranslatorInserted(EntityUid translator, HandheldTranslatorComponent component, EntGotInsertedIntoContainerMessage args)
{
if (args.Container.Owner is not {Valid: true} holder || !HasComp<LanguageSpeakerComponent>(holder))
return;
var intrinsic = EnsureComp<HoldsTranslatorComponent>(holder);
intrinsic.Translators.Add((translator, component));
_language.UpdateEntityLanguages(holder);
}
private void OnTranslatorParentChanged(EntityUid translator, HandheldTranslatorComponent component, EntParentChangedMessage args)
{
if (!HasComp<HoldsTranslatorComponent>(args.OldParent))
return;
// Update the translator on the next tick - this is necessary because there's a good chance the removal from a container
// Was caused by the player moving the translator within their inventory rather than removing it.
// If that is not the case, then OnProxyDetermineLanguages will remove this translator from HoldsTranslatorComponent.Translators.
Timer.Spawn(0, () =>
{
if (Exists(args.OldParent) && HasComp<LanguageSpeakerComponent>(args.OldParent))
_language.UpdateEntityLanguages(args.OldParent.Value);
});
}
private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponent translatorComp, ActivateInWorldEvent args)
{
if (!translatorComp.ToggleOnInteract)
return;
// This will show a popup if false
var hasPower = _powerCell.HasDrawCharge(translator);
var isEnabled = !translatorComp.Enabled && hasPower;
translatorComp.Enabled = isEnabled;
_powerCell.SetPowerCellDrawEnabled(translator, isEnabled);
if (_containers.TryGetContainingContainer(translator, out var holderCont)
&& holderCont.Owner is var holder
&& TryComp<LanguageSpeakerComponent>(holder, out var languageComp))
{
// The first new spoken language added by this translator, or null
var firstNewLanguage = translatorComp.SpokenLanguages.FirstOrDefault(it => !languageComp.SpokenLanguages.Contains(it));
_language.UpdateEntityLanguages(holder);
// Update the current language of the entity if necessary
if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {})
_language.SetLanguage(holder, firstNewLanguage, languageComp);
}
OnAppearanceChange(translator, translatorComp);
if (hasPower)
{
var loc = isEnabled ? "translator-component-turnon" : "translator-component-shutoff";
var message = Loc.GetString(loc, ("translator", translator));
_popup.PopupEntity(message, translator, args.User);
}
}
private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args)
{
component.Enabled = false;
_powerCell.SetPowerCellDrawEnabled(translator, false);
OnAppearanceChange(translator, component);
if (_containers.TryGetContainingContainer(translator, out var holderCont) && HasComp<LanguageSpeakerComponent>(holderCont.Owner))
_language.UpdateEntityLanguages(holderCont.Owner);
}
private void CopyLanguages(BaseTranslatorComponent from, DetermineEntityLanguagesEvent to, LanguageKnowledgeComponent knowledge)
{
var addSpoken = CheckLanguagesMatch(from.RequiredLanguages, knowledge.SpokenLanguages, from.RequiresAllLanguages);
var addUnderstood = CheckLanguagesMatch(from.RequiredLanguages, knowledge.UnderstoodLanguages, from.RequiresAllLanguages);
if (addSpoken)
foreach (var language in from.SpokenLanguages)
to.SpokenLanguages.Add(language);
if (addUnderstood)
foreach (var language in from.UnderstoodLanguages)
to.UnderstoodLanguages.Add(language);
}
/// <summary>
/// Checks whether any OR all required languages are provided. Used for utility purposes.
/// </summary>
public static bool CheckLanguagesMatch(ICollection<string> required, ICollection<string> provided, bool requireAll)
{
if (required.Count == 0)
return true;
return requireAll
? required.All(provided.Contains)
: required.Any(provided.Contains);
}
}