Skip to content

Commit

Permalink
Non-Destructive Universal Language (#899)
Browse files Browse the repository at this point in the history
# 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

:cl:
- 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 <[email protected]>
Co-authored-by: Mnemotechnican <[email protected]>
Co-authored-by: FoxxoTrystan <[email protected]>
Co-authored-by: DEATHB4DEFEAT <[email protected]>
  • Loading branch information
4 people authored Sep 20, 2024
1 parent 7bce1e9 commit 8b5dcd6
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 113 deletions.
2 changes: 1 addition & 1 deletion Content.Server/Chemistry/ReagentEffects/MakeSentient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void Effect(ReagentEffectArgs args)
if (!knowledge.SpokenLanguages.Contains(fallback))
knowledge.SpokenLanguages.Add(fallback);

IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>().UpdateEntityLanguages(uid, speaker);
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>().UpdateEntityLanguages(uid);

// Stops from adding a ghost role to things like people who already have a mind
if (entityManager.TryGetComponent<MindContainerComponent>(uid, out var mindContainer) && mindContainer.HasMind)
Expand Down
7 changes: 1 addition & 6 deletions Content.Server/Language/LanguageSystem.Networking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,7 @@ private void SendLanguageStateToClient(ICommonSession session, LanguageSpeakerCo
// TODO this is really stupid and can be avoided if we just make everything shared...
private void SendLanguageStateToClient(EntityUid uid, ICommonSession session, LanguageSpeakerComponent? component = null)
{
var isUniversal = HasComp<UniversalLanguageSpeakerComponent>(uid);
if (!isUniversal)
Resolve(uid, ref component, logMissing: false);

// I really don't want to call 3 getter methods here, so we'll just have this slightly hardcoded solution
var message = isUniversal || component == null
var message = !Resolve(uid, ref component, logMissing: false)
? new LanguagesUpdatedMessage(UniversalPrototype, [UniversalPrototype], [UniversalPrototype])
: new LanguagesUpdatedMessage(component.CurrentLanguage, component.SpokenLanguages, component.UnderstoodLanguages);

Expand Down
70 changes: 34 additions & 36 deletions Content.Server/Language/LanguageSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
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;

Expand All @@ -16,8 +15,19 @@ public override void Initialize()
InitializeNet();

SubscribeLocalEvent<LanguageSpeakerComponent, ComponentInit>(OnInitLanguageSpeaker);
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, MapInitEvent>(OnUniversalInit);
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, ComponentShutdown>(OnUniversalShutdown);
}

private void OnUniversalShutdown(EntityUid uid, UniversalLanguageSpeakerComponent component, ComponentShutdown args)
{
RemoveLanguage(uid, UniversalPrototype);
}

private void OnUniversalInit(EntityUid uid, UniversalLanguageSpeakerComponent component, MapInitEvent args)
{
AddLanguage(uid, UniversalPrototype);
}

#region public api

Expand Down Expand Up @@ -48,10 +58,9 @@ public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponen
/// </summary>
public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? component = null)
{
if (HasComp<UniversalLanguageSpeakerComponent>(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<LanguagePrototype>(component.CurrentLanguage, out var proto))
if (!Resolve(speaker, ref component, logMissing: false)
|| string.IsNullOrEmpty(component.CurrentLanguage)
|| !_prototype.TryIndex<LanguagePrototype>(component.CurrentLanguage, out var proto))
return Universal;

return proto;
Expand All @@ -63,13 +72,10 @@ public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent
/// <remarks>Typically, checking <see cref="LanguageSpeakerComponent.SpokenLanguages"/> is sufficient.</remarks>
public List<string> GetSpokenLanguages(EntityUid uid)
{
if (HasComp<UniversalLanguageSpeakerComponent>(uid))
return [UniversalPrototype];
if (!TryComp<LanguageSpeakerComponent>(uid, out var component))
return [];

if (TryComp<LanguageSpeakerComponent>(uid, out var component))
return component.SpokenLanguages;

return [];
return component.SpokenLanguages;
}

/// <summary>
Expand All @@ -78,21 +84,17 @@ public List<string> GetSpokenLanguages(EntityUid uid)
/// <remarks>Typically, checking <see cref="LanguageSpeakerComponent.UnderstoodLanguages"/> is sufficient.</remarks>
public List<string> GetUnderstoodLanguages(EntityUid uid)
{
if (HasComp<UniversalLanguageSpeakerComponent>(uid))
return [UniversalPrototype]; // This one is tricky because... well, they understand all of them, not just one.

if (TryComp<LanguageSpeakerComponent>(uid, out var component))
return component.UnderstoodLanguages;
if (!TryComp<LanguageSpeakerComponent>(uid, out var component))
return [];

return [];
return component.UnderstoodLanguages;
}

public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? component = null)
{
if (!CanSpeak(speaker, language) || (HasComp<UniversalLanguageSpeakerComponent>(speaker) && language != UniversalPrototype))
return;

if (!Resolve(speaker, ref component) || component.CurrentLanguage == language)
if (!CanSpeak(speaker, language)
|| !Resolve(speaker, ref component)
|| component.CurrentLanguage == language)
return;

component.CurrentLanguage = language;
Expand All @@ -106,20 +108,18 @@ public void AddLanguage(
EntityUid uid,
string language,
bool addSpoken = true,
bool addUnderstood = true,
LanguageKnowledgeComponent? knowledge = null,
LanguageSpeakerComponent? speaker = null)
bool addUnderstood = true)
{
if (knowledge == null)
knowledge = EnsureComp<LanguageKnowledgeComponent>(uid);
EnsureComp<LanguageKnowledgeComponent>(uid, out var knowledge);
EnsureComp<LanguageSpeakerComponent>(uid);

if (addSpoken && !knowledge.SpokenLanguages.Contains(language))
knowledge.SpokenLanguages.Add(language);

if (addUnderstood && !knowledge.UnderstoodLanguages.Contains(language))
knowledge.UnderstoodLanguages.Add(language);

UpdateEntityLanguages(uid, speaker);
UpdateEntityLanguages(uid);
}

/// <summary>
Expand All @@ -129,20 +129,18 @@ public void RemoveLanguage(
EntityUid uid,
string language,
bool removeSpoken = true,
bool removeUnderstood = true,
LanguageKnowledgeComponent? knowledge = null,
LanguageSpeakerComponent? speaker = null)
bool removeUnderstood = true)
{
if (knowledge == null)
knowledge = EnsureComp<LanguageKnowledgeComponent>(uid);
if (!TryComp<LanguageKnowledgeComponent>(uid, out var knowledge))
return;

if (removeSpoken)
knowledge.SpokenLanguages.Remove(language);

if (removeUnderstood)
knowledge.UnderstoodLanguages.Remove(language);

UpdateEntityLanguages(uid, speaker);
UpdateEntityLanguages(uid);
}

/// <summary>
Expand All @@ -168,9 +166,9 @@ public bool EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp
/// <summary>
/// Immediately refreshes the cached lists of spoken and understood languages for the given entity.
/// </summary>
public void UpdateEntityLanguages(EntityUid entity, LanguageSpeakerComponent? languages = null)
public void UpdateEntityLanguages(EntityUid entity)
{
if (!Resolve(entity, ref languages))
if (!TryComp<LanguageSpeakerComponent>(entity, out var languages))
return;

var ev = new DetermineEntityLanguagesEvent();
Expand Down Expand Up @@ -205,7 +203,7 @@ private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent compo
if (string.IsNullOrEmpty(component.CurrentLanguage))
component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype);

UpdateEntityLanguages(uid, component);
UpdateEntityLanguages(uid);
}

#endregion
Expand Down
10 changes: 5 additions & 5 deletions Content.Server/Language/TranslatorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ private void OnTranslatorParentChanged(EntityUid translator, HandheldTranslatorC
// If that is not the case, then OnProxyDetermineLanguages will remove this translator from HoldsTranslatorComponent.Translators.
Timer.Spawn(0, () =>
{
if (Exists(args.OldParent) && TryComp<LanguageSpeakerComponent>(args.OldParent, out var speaker))
_language.UpdateEntityLanguages(args.OldParent.Value, speaker);
if (Exists(args.OldParent) && HasComp<LanguageSpeakerComponent>(args.OldParent))
_language.UpdateEntityLanguages(args.OldParent.Value);
});
}

Expand All @@ -108,7 +108,7 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen
{
// 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);
_language.UpdateEntityLanguages(holder);

// Update the current language of the entity if necessary
if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {})
Expand All @@ -131,8 +131,8 @@ private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorCompon
_powerCell.SetPowerCellDrawEnabled(translator, false);
OnAppearanceChange(translator, component);

if (_containers.TryGetContainingContainer(translator, out var holderCont) && TryComp<LanguageSpeakerComponent>(holderCont.Owner, out var languageComp))
_language.UpdateEntityLanguages(holderCont.Owner, languageComp);
if (_containers.TryGetContainingContainer(translator, out var holderCont) && HasComp<LanguageSpeakerComponent>(holderCont.Owner))
_language.UpdateEntityLanguages(holderCont.Owner);
}

private void CopyLanguages(BaseTranslatorComponent from, DetermineEntityLanguagesEvent to, LanguageKnowledgeComponent knowledge)
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Traits/Assorted/ForeignerTraitSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private void OnSpawn(Entity<ForeignerTraitComponent> entity, ref ComponentInit a

if (TryGiveTranslator(entity.Owner, entity.Comp.BaseTranslator, entity.Comp.BaseLanguage, alternateLanguage, out var translator))
{
_languages.RemoveLanguage(entity, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand, knowledge);
_languages.RemoveLanguage(entity, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand);
}
}

Expand Down

This file was deleted.

35 changes: 0 additions & 35 deletions Content.Server/Traits/Assorted/LanguageKnowledgeModifierSystem.cs

This file was deleted.

70 changes: 70 additions & 0 deletions Content.Server/Traits/TraitSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Robust.Shared.Utility;
using Content.Server.Abilities.Psionics;
using Content.Shared.Psionics;
using Content.Server.Language;
using Content.Shared.Mood;

namespace Content.Server.Traits;
Expand All @@ -26,6 +27,7 @@ public sealed class TraitSystem : EntitySystem
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PsionicAbilitiesSystem _psionicAbilities = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly LanguageSystem _languageSystem = default!;

public override void Initialize()
{
Expand Down Expand Up @@ -66,6 +68,8 @@ public void AddTrait(EntityUid uid, TraitPrototype traitPrototype)
AddTraitComponents(uid, traitPrototype);
AddTraitActions(uid, traitPrototype);
AddTraitPsionics(uid, traitPrototype);
AddTraitLanguage(uid, traitPrototype);
RemoveTraitLanguage(uid, traitPrototype);
AddTraitMoodlets(uid, traitPrototype);
}

Expand Down Expand Up @@ -142,6 +146,72 @@ public void AddTraitPsionics(EntityUid uid, TraitPrototype traitPrototype)
_psionicAbilities.InitializePsionicPower(uid, psionicPower, false);
}

/// <summary>
/// Initialize languages given by a Trait.
/// </summary>
private void AddTraitLanguage(EntityUid uid, TraitPrototype traitPrototype)
{
AddTraitLanguagesSpoken(uid, traitPrototype);
AddTraitLanguagesUnderstood(uid, traitPrototype);
}

/// <summary>
/// If a trait includes any Spoken Languages, this sends them to LanguageSystem to be initialized.
/// </summary>
public void AddTraitLanguagesSpoken(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.LanguagesSpoken is null)
return;

foreach (var language in traitPrototype.LanguagesSpoken)
_languageSystem.AddLanguage(uid, language, true, false);
}

/// <summary>
/// If a trait includes any Understood Languages, this sends them to LanguageSystem to be initialized.
/// </summary>
public void AddTraitLanguagesUnderstood(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.LanguagesUnderstood is null)
return;

foreach (var language in traitPrototype.LanguagesUnderstood)
_languageSystem.AddLanguage(uid, language, false, true);
}

/// <summary>
/// Remove Languages given by a Trait.
/// </summary>
private void RemoveTraitLanguage(EntityUid uid, TraitPrototype traitPrototype)
{
RemoveTraitLanguagesSpoken(uid, traitPrototype);
RemoveTraitLanguagesUnderstood(uid, traitPrototype);
}

/// <summary>
/// Removes any Spoken Languages if defined by a trait.
/// </summary>
public void RemoveTraitLanguagesSpoken(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.RemoveLanguagesSpoken is null)
return;

foreach (var language in traitPrototype.RemoveLanguagesSpoken)
_languageSystem.RemoveLanguage(uid, language, true, false);
}

/// <summary>
/// Removes any Understood Languages if defined by a trait.
/// </summary>
public void RemoveTraitLanguagesUnderstood(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.RemoveLanguagesUnderstood is null)
return;

foreach (var language in traitPrototype.RemoveLanguagesUnderstood)
_languageSystem.RemoveLanguage(uid, language, false, true);
}

/// <summary>
/// If a trait includes any moodlets, this adds the moodlets to the receiving entity.
/// While I can't stop you, you shouldn't use this to add temporary moodlets.
Expand Down
Loading

0 comments on commit 8b5dcd6

Please sign in to comment.