Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Translators Not Working Correctly Under Certain Conditions #834

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 53 additions & 99 deletions Content.Server/Language/TranslatorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
using Content.Shared.Language.Systems;
using Content.Shared.PowerCell;
using Content.Shared.Language.Components.Translators;
using Robust.Shared.Containers;

namespace Content.Server.Language;

// This does not support holding multiple translators at once.
// That shouldn't be an issue for now, but it needs to be fixed later.
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!;
Expand All @@ -24,65 +24,64 @@ public override void Initialize()
base.Initialize();

SubscribeLocalEvent<IntrinsicTranslatorComponent, DetermineEntityLanguagesEvent>(OnDetermineLanguages);
SubscribeLocalEvent<HoldsTranslatorComponent, DetermineEntityLanguagesEvent>(OnDetermineLanguages);
SubscribeLocalEvent<ImplantedTranslatorComponent, DetermineEntityLanguagesEvent>(OnDetermineLanguages);
SubscribeLocalEvent<HoldsTranslatorComponent, DetermineEntityLanguagesEvent>(OnProxyDetermineLanguages);

SubscribeLocalEvent<HandheldTranslatorComponent, EntGotInsertedIntoContainerMessage>(OnTranslatorInserted);
SubscribeLocalEvent<HandheldTranslatorComponent, EntGotRemovedFromContainerMessage>(OnTranslatorRemoved);
SubscribeLocalEvent<HandheldTranslatorComponent, ActivateInWorldEvent>(OnTranslatorToggle);
SubscribeLocalEvent<HandheldTranslatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);

SubscribeLocalEvent<HandheldTranslatorComponent, InteractHandEvent>(OnTranslatorInteract);
SubscribeLocalEvent<HandheldTranslatorComponent, DroppedEvent>(OnTranslatorDropped);
}

private void OnDetermineLanguages(EntityUid uid, IntrinsicTranslatorComponent component, DetermineEntityLanguagesEvent ev)
{
if (!component.Enabled || !TryComp<LanguageSpeakerComponent>(uid, out var speaker))
if (!component.Enabled
|| component.LifeStage >= ComponentLifeStage.Removing
|| !TryComp<LanguageKnowledgeComponent>(uid, out var knowledge)
|| !_powerCell.HasActivatableCharge(uid))
return;

if (!_powerCell.HasActivatableCharge(uid))
CopyLanguages(component, ev, knowledge);
}

private void OnProxyDetermineLanguages(EntityUid uid, HoldsTranslatorComponent component, DetermineEntityLanguagesEvent ev)
{
if (!TryComp<LanguageKnowledgeComponent>(uid, out var knowledge))
return;

// The idea here is as follows:
// Required languages are languages that are required to operate the translator.
// The translator has a limited number of languages it can translate to and translate from.
// If the wielder understands the language of the translator, they will be able to understand translations provided by it
// If the wielder also speaks that language, they will be able to use it to translate their own speech by "speaking" in that language
var addSpoken = CheckLanguagesMatch(component.RequiredLanguages, speaker.SpokenLanguages, component.RequiresAllLanguages);
var addUnderstood = CheckLanguagesMatch(component.RequiredLanguages, speaker.UnderstoodLanguages, component.RequiresAllLanguages);
foreach (var (translator, translatorComp) in component.Translators.ToArray())
{
if (!translatorComp.Enabled || !_powerCell.HasActivatableCharge(uid))
continue;

if (addSpoken)
foreach (var language in component.SpokenLanguages)
ev.SpokenLanguages.Add(language);
if (!_containers.TryGetContainingContainer(translator, out var container) || container.Owner != uid)
{
component.Translators.RemoveWhere(it => it.Owner == translator);
continue;
}

if (addUnderstood)
foreach (var language in component.UnderstoodLanguages)
ev.UnderstoodLanguages.Add(language);
CopyLanguages(translatorComp, ev, knowledge);
}
}

private void OnTranslatorInteract(EntityUid translator, HandheldTranslatorComponent component, InteractHandEvent args)
private void OnTranslatorInserted(EntityUid translator, HandheldTranslatorComponent component, EntGotInsertedIntoContainerMessage args)
{
var holder = args.User;
if (!EntityManager.HasComponent<LanguageSpeakerComponent>(holder))
if (args.Container.Owner is not {Valid: true} holder
|| !EntityManager.HasComponent<LanguageSpeakerComponent>(holder))
return;

var intrinsic = EnsureComp<HoldsTranslatorComponent>(holder);
UpdateBoundIntrinsicComp(component, intrinsic, component.Enabled);
intrinsic.Translators.Add((translator, component));

_language.UpdateEntityLanguages(holder);
}

private void OnTranslatorDropped(EntityUid translator, HandheldTranslatorComponent component, DroppedEvent args)
private void OnTranslatorRemoved(EntityUid translator, HandheldTranslatorComponent component, EntGotRemovedFromContainerMessage args)
{
var holder = args.User;
if (!EntityManager.TryGetComponent<HoldsTranslatorComponent>(holder, out var intrinsic))
if (args.Container.Owner is not {Valid: true} holder
|| !EntityManager.TryGetComponent<HoldsTranslatorComponent>(holder, out var intrinsic))
return;

if (intrinsic.Issuer == component)
{
intrinsic.Enabled = false;
RemCompDeferred(holder, intrinsic);
}

intrinsic.Translators.RemoveWhere(it => it.Owner == translator);
_language.UpdateEntityLanguages(holder);
}

Expand All @@ -93,53 +92,31 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen

// This will show a popup if false
var hasPower = _powerCell.HasDrawCharge(translator);
var isEnabled = !translatorComp.Enabled && hasPower;

if (Transform(args.Target).ParentUid is { Valid: true } holder
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))
{
// This translator is held by a language speaker and thus has an intrinsic counterpart bound to it.
// Make sure it's up-to-date.
var intrinsic = EnsureComp<HoldsTranslatorComponent>(holder);
var isEnabled = !translatorComp.Enabled;
if (intrinsic.Issuer != translatorComp)
{
// The intrinsic comp wasn't owned by this handheld translator, so this wasn't the active translator.
// Thus, the intrinsic comp needs to be turned on regardless of its previous state.
intrinsic.Issuer = translatorComp;
isEnabled = true;
}
isEnabled &= hasPower;

UpdateBoundIntrinsicComp(translatorComp, intrinsic, isEnabled);
translatorComp.Enabled = isEnabled;
_powerCell.SetPowerCellDrawEnabled(translator, isEnabled);

// The first new spoken language added by this translator, or null
var firstNewLanguage = translatorComp.SpokenLanguages.FirstOrDefault(it => !languageComp.SpokenLanguages.Contains(it));

_language.UpdateEntityLanguages(holder, languageComp);

// Update the current language of the entity if necessary
if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {})
_language.SetLanguage(holder, firstNewLanguage, languageComp);
}
else
{
// This is a standalone translator (e.g. lying on the ground), toggle its state.
translatorComp.Enabled = !translatorComp.Enabled && hasPower;
_powerCell.SetPowerCellDrawEnabled(translator, !translatorComp.Enabled && hasPower);
}

OnAppearanceChange(translator, translatorComp);

if (hasPower)
{
var message = Loc.GetString(
translatorComp.Enabled
? "translator-component-turnon"
: "translator-component-shutoff",
("translator", translatorComp.Owner));
_popup.PopupEntity(message, translatorComp.Owner, args.User);
var loc = isEnabled ? "translator-component-turnon" : "translator-component-shutoff";
var message = Loc.GetString(loc, ("translator", translator));
_popup.PopupEntity(message, translator, args.User);
}
}

Expand All @@ -148,43 +125,20 @@ private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorCompon
component.Enabled = false;
_powerCell.SetPowerCellDrawEnabled(translator, false);
OnAppearanceChange(translator, component);

if (Transform(translator).ParentUid is { Valid: true } holder
&& TryComp<LanguageSpeakerComponent>(holder, out var languageComp))
{
if (!EntityManager.TryGetComponent<HoldsTranslatorComponent>(holder, out var intrinsic))
return;

if (intrinsic.Issuer == component)
{
intrinsic.Enabled = false;
RemComp(holder, intrinsic);
}

_language.UpdateEntityLanguages(holder, languageComp);
}
}

/// <summary>
/// Copies the state from the handheld to the intrinsic component
/// </summary>
private void UpdateBoundIntrinsicComp(HandheldTranslatorComponent comp, HoldsTranslatorComponent intrinsic, bool isEnabled)
private void CopyLanguages(BaseTranslatorComponent from, DetermineEntityLanguagesEvent to, LanguageKnowledgeComponent knowledge)
{
if (isEnabled)
{
intrinsic.SpokenLanguages = [..comp.SpokenLanguages];
intrinsic.UnderstoodLanguages = [..comp.UnderstoodLanguages];
intrinsic.RequiredLanguages = [..comp.RequiredLanguages];
}
else
{
intrinsic.SpokenLanguages.Clear();
intrinsic.UnderstoodLanguages.Clear();
intrinsic.RequiredLanguages.Clear();
}
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);

intrinsic.Enabled = isEnabled;
intrinsic.Issuer = comp;
if (addUnderstood)
foreach (var language in from.UnderstoodLanguages)
to.UnderstoodLanguages.Add(language);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Content.Shared.Language.Components.Translators;

/// <summary>
/// Applied internally to the holder of [HandheldTranslatorComponent].
/// Do not use directly. Use [HandheldTranslatorComponent] instead.
/// Applied internally to the holder of an entity with [HandheldTranslatorComponent].
/// </summary>
[RegisterComponent]
public sealed partial class HoldsTranslatorComponent : IntrinsicTranslatorComponent
public sealed partial class HoldsTranslatorComponent : Component
{
public Component? Issuer = null;
[NonSerialized]
public HashSet<Entity<HandheldTranslatorComponent>> Translators = new();
}

This file was deleted.

Loading