mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 05:27:38 +03:00
# Description The language PR was merged early and OH GOD I ALREADY REGRET IT This PR is intended to provide the missing refactors and address the issues that were missed due to the early merge. --- # TODO - [X] Introduced a polymorphic obfuscation property to the LanguagePrototype - now it supports more than just 2 hardcoded methods, and each method can be configured per-language. Currently there are 3 obfuscation methods: replacement (same as replacement accent), obuscation by syllables and obfuscation by phrases. - [X] Refactored the existing obfuscation methods to not be a big hardcoded mess. - [X] Updated the existing languages accordingly: animalistic languages are now less of an unreadable mess and include less syllables. Certain languages like binary and snake seriously benefit from that. - [X] Refactored the existing commands in response to the never-addressed review (it got lost among hundreds of others) - [X] Refactored the commands to be more user-friendly (you can now use the number of the language in saylang and languageselect which can allow using keybinds to switch between languages) - [X] Moved a lot of obfuscation-related stuff from server to shared. The actual obfuscation process, however, is still done on the server. That may or may not be subject to change, too. - [X] Refactored the entire process of resolution of entities' languages. Instead of raising an event every time it's required to learn what languages an entity knows, the lists of ALL languages available to the entity (including via translators) is stored in LanguageSpeakerComponent and only updated when necessary (e.g. when a translator gets toggled). The list of languages the entity knows on its own is now stored in LanguageKnowledgeComponent. - [X] Made handheld translators automatically change your current language when activated. - [X] Rewrote the translator implanter system, now using the real implants and implanters - [ ] Rebalance science stuff (translators are incredibly expensive for what they're worth) - [ ] Uhhh stuff --- <details><summary><h1>Media</h1></summary> <p> N/A for now </p> </details> --- # Changelog 🆑 - tweak: Translator implants are now proper implants that can be removed. - tweak: Animalistic languages should now look less messy. - fix: Hopefully fixed language menu desync and other issues. --------- Signed-off-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com>
203 lines
8.3 KiB
C#
203 lines
8.3 KiB
C#
using System.Linq;
|
|
using Content.Server.Language.Events;
|
|
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.Events;
|
|
using Content.Shared.Language.Systems;
|
|
using Content.Shared.PowerCell;
|
|
using Content.Shared.Language.Components.Translators;
|
|
using Robust.Shared.Utility;
|
|
|
|
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 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>(OnDetermineLanguages);
|
|
SubscribeLocalEvent<ImplantedTranslatorComponent, DetermineEntityLanguagesEvent>(OnDetermineLanguages);
|
|
|
|
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))
|
|
return;
|
|
|
|
if (!_powerCell.HasActivatableCharge(uid))
|
|
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);
|
|
|
|
if (addSpoken)
|
|
foreach (var language in component.SpokenLanguages)
|
|
ev.SpokenLanguages.Add(language);
|
|
|
|
if (addUnderstood)
|
|
foreach (var language in component.UnderstoodLanguages)
|
|
ev.UnderstoodLanguages.Add(language);
|
|
}
|
|
|
|
private void OnTranslatorInteract(EntityUid translator, HandheldTranslatorComponent component, InteractHandEvent args)
|
|
{
|
|
var holder = args.User;
|
|
if (!EntityManager.HasComponent<LanguageSpeakerComponent>(holder))
|
|
return;
|
|
|
|
var intrinsic = EnsureComp<HoldsTranslatorComponent>(holder);
|
|
UpdateBoundIntrinsicComp(component, intrinsic, component.Enabled);
|
|
|
|
_language.UpdateEntityLanguages(holder);
|
|
}
|
|
|
|
private void OnTranslatorDropped(EntityUid translator, HandheldTranslatorComponent component, DroppedEvent args)
|
|
{
|
|
var holder = args.User;
|
|
if (!EntityManager.TryGetComponent<HoldsTranslatorComponent>(holder, out var intrinsic))
|
|
return;
|
|
|
|
if (intrinsic.Issuer == component)
|
|
{
|
|
intrinsic.Enabled = false;
|
|
RemCompDeferred(holder, intrinsic);
|
|
}
|
|
|
|
_language.UpdateEntityLanguages(holder);
|
|
}
|
|
|
|
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);
|
|
|
|
if (Transform(args.Target).ParentUid is { Valid: true } 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);
|
|
}
|
|
}
|
|
|
|
private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args)
|
|
{
|
|
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)
|
|
{
|
|
if (isEnabled)
|
|
{
|
|
intrinsic.SpokenLanguages = [..comp.SpokenLanguages];
|
|
intrinsic.UnderstoodLanguages = [..comp.UnderstoodLanguages];
|
|
}
|
|
else
|
|
{
|
|
intrinsic.SpokenLanguages.Clear();
|
|
intrinsic.UnderstoodLanguages.Clear();
|
|
}
|
|
|
|
intrinsic.Enabled = isEnabled;
|
|
intrinsic.Issuer = comp;
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|