Skip to content

Commit

Permalink
Merge branch 'refactor/languages' into ExperimantalLanguage
Browse files Browse the repository at this point in the history
  • Loading branch information
FoxxoTrystan committed Jul 4, 2024
2 parents 2f3218d + d9d74de commit 86f0c86
Show file tree
Hide file tree
Showing 59 changed files with 1,396 additions and 1,112 deletions.
13 changes: 5 additions & 8 deletions Content.Client/Language/LanguageMenuWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
using Content.Client.Language.Systems;
using Content.Shared.Language;
using Content.Shared.Language.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
using Robust.Shared.Utility;
using Serilog;
using static Content.Shared.Language.Systems.SharedLanguageSystem;

namespace Content.Client.Language;

Expand Down Expand Up @@ -121,8 +115,11 @@ private void AddLanguageEntry(string language)
private void OnLanguageChosen(string id)
{
var proto = _clientLanguageSystem.GetLanguagePrototype(id);
if (proto != null)
_clientLanguageSystem.RequestSetLanguage(proto);
if (proto == null)
return;

_clientLanguageSystem.RequestSetLanguage(proto);
UpdateState(id, _clientLanguageSystem.SpokenLanguages);
}


Expand Down
1 change: 0 additions & 1 deletion Content.Client/Language/Systems/LanguageSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Content.Shared.Language.Events;
using Content.Shared.Language.Systems;
using Robust.Client;
using Robust.Shared.Console;

namespace Content.Client.Language.Systems;

Expand Down
8 changes: 0 additions & 8 deletions Content.Client/Language/Systems/TranslatorImplanterSystem.cs

This file was deleted.

4 changes: 2 additions & 2 deletions Content.Server/Chat/Systems/ChatSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ private void SendEntityWhisper(
if (MessageRangeCheck(session, data, range) != MessageRangeCheckResult.Full)
continue; // Won't get logged to chat, and ghosts are too far away to see the pop-up, so we just won't send it to them.

var canUnderstandLanguage = _language.CanUnderstand(listener, language);
var canUnderstandLanguage = _language.CanUnderstand(listener, language.ID);
// How the entity perceives the message depends on whether it can understand its language
var perceivedMessage = canUnderstandLanguage ? message : languageObfuscatedMessage;

Expand Down Expand Up @@ -717,7 +717,7 @@ private void SendInVoiceRange(ChatChannel channel, string name, string message,


// If the channel does not support languages, or the entity can understand the message, send the original message, otherwise send the obfuscated version
if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language))
if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language.ID))
{
_chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author);
}
Expand Down
23 changes: 13 additions & 10 deletions Content.Server/Chemistry/ReagentEffects/MakeSentient.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using System.Linq;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Language;
using Content.Server.Language.Events;
using Content.Server.Speech.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Language;
using Content.Shared.Language.Systems;
using Content.Shared.Mind.Components;
using Robust.Shared.Prototypes;
using Content.Server.Psionics; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic.
using Content.Shared.Humanoid; //Delta-V - Banning humanoids from becoming ghost roles.
using Content.Server.Psionics;
using Content.Shared.Body.Part; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic.
using Content.Shared.Humanoid;
using Content.Shared.Language.Components; //Delta-V - Banning humanoids from becoming ghost roles.
using Content.Shared.Language.Events;

namespace Content.Server.Chemistry.ReagentEffects;
Expand All @@ -28,19 +32,18 @@ public override void Effect(ReagentEffectArgs args)
entityManager.RemoveComponent<ReplacementAccentComponent>(uid);
entityManager.RemoveComponent<MonkeyAccentComponent>(uid);

// Make sure the entity knows at least fallback (Galactic Common)
var speaker = entityManager.EnsureComponent<LanguageSpeakerComponent>(uid);
var knowledge = entityManager.EnsureComponent<LanguageKnowledgeComponent>(uid);
var fallback = SharedLanguageSystem.FallbackLanguagePrototype;

if (!speaker.UnderstoodLanguages.Contains(fallback))
speaker.UnderstoodLanguages.Add(fallback);
if (!knowledge.UnderstoodLanguages.Contains(fallback))
knowledge.UnderstoodLanguages.Add(fallback);

if (!speaker.SpokenLanguages.Contains(fallback))
{
speaker.CurrentLanguage = fallback;
speaker.SpokenLanguages.Add(fallback);
}
if (!knowledge.SpokenLanguages.Contains(fallback))
knowledge.SpokenLanguages.Add(fallback);

args.EntityManager.EventBus.RaiseLocalEvent(uid, new LanguagesUpdateEvent(), true);
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>().UpdateEntityLanguages(uid, speaker);

// 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
27 changes: 23 additions & 4 deletions Content.Server/Language/Commands/ListLanguagesCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Language;
using Robust.Shared.Console;
using Robust.Shared.Enums;

Expand Down Expand Up @@ -30,10 +30,29 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
}

var languages = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>();
var currentLang = languages.GetLanguage(playerEntity).ID;

var (spokenLangs, knownLangs) = languages.GetAllLanguages(playerEntity);
shell.WriteLine(Loc.GetString("command-language-spoken"));
var spoken = languages.GetSpokenLanguages(playerEntity);
for (int i = 0; i < spoken.Count; i++)
{
var lang = spoken[i];
shell.WriteLine(lang == currentLang
? Loc.GetString("command-language-current-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang)))
: Loc.GetString("command-language-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang))));
}

shell.WriteLine("Spoken:\n" + string.Join("\n", spokenLangs));
shell.WriteLine("Understood:\n" + string.Join("\n", knownLangs));
shell.WriteLine(Loc.GetString("command-language-understood"));
var understood = languages.GetUnderstoodLanguages(playerEntity);
for (int i = 0; i < understood.Count; i++)
{
var lang = understood[i];
shell.WriteLine(Loc.GetString("command-language-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang))));
}
}

private string LanguageName(string id)
{
return Loc.GetString($"language-{id}-name");
}
}
6 changes: 2 additions & 4 deletions Content.Server/Language/Commands/SayLanguageCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
if (args.Length < 2)
return;

var languageId = args[0];
var message = string.Join(" ", args, startIndex: 1, count: args.Length - 1).Trim();

if (string.IsNullOrEmpty(message))
Expand All @@ -41,10 +40,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
var languages = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>();
var chats = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>();

var language = languages.GetLanguagePrototype(languageId);
if (language == null || !languages.CanSpeak(playerEntity, language.ID))
if (!SelectLanguageCommand.TryParseLanguageArgument(languages, playerEntity, args[0], out var failReason, out var language))
{
shell.WriteError($"Language {languageId} is invalid or you cannot speak it!");
shell.WriteError(failReason);
return;
}

Expand Down
50 changes: 45 additions & 5 deletions Content.Server/Language/Commands/SelectLanguageCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Language;
using Robust.Shared.Console;
using Robust.Shared.Enums;

Expand Down Expand Up @@ -32,17 +34,55 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
if (args.Length < 1)
return;

var languageId = args[0];

var languages = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>();

var language = languages.GetLanguagePrototype(languageId);
if (language == null || !languages.CanSpeak(playerEntity, language.ID))
if (!TryParseLanguageArgument(languages, playerEntity, args[0], out var failReason, out var language))
{
shell.WriteError($"Language {languageId} is invalid or you cannot speak it!");
shell.WriteError(failReason);
return;
}

languages.SetLanguage(playerEntity, language.ID);
}

// TODO: find a better place for this method
/// <summary>
/// Tries to parse the input argument as either a language ID or the position of the language in the list of languages
/// the entity can speak. Returns true if sucessful.
/// </summary>
public static bool TryParseLanguageArgument(
LanguageSystem languageSystem,
EntityUid speaker,
string input,
[NotNullWhen(false)] out string? failureReason,
[NotNullWhen(true)] out LanguagePrototype? language)
{
failureReason = null;
language = null;

if (int.TryParse(input, out var num))
{
// The argument is a number
var spoken = languageSystem.GetSpokenLanguages(speaker);
if (num > 0 && num - 1 < spoken.Count)
language = languageSystem.GetLanguagePrototype(spoken[num - 1]);

if (language != null) // the ability to speak it is implied
return true;

failureReason = Loc.GetString("command-language-invalid-number", ("total", spoken.Count));
return false;
}
else
{
// The argument is a language ID
language = languageSystem.GetLanguagePrototype(input);

if (language != null && languageSystem.CanSpeak(speaker, language.ID))
return true;

failureReason = Loc.GetString("command-language-invalid-language", ("id", input));
return false;
}
}
}
32 changes: 14 additions & 18 deletions Content.Server/Language/DetermineEntityLanguagesEvent.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
using Content.Shared.Language;

namespace Content.Server.Language;

/// <summary>
/// Raised in order to determine the language an entity speaks at the current moment,
/// as well as the list of all languages the entity may speak and understand.
/// Raised in order to determine the list of languages the entity can speak and understand at the given moment.
/// Typically raised on an entity after a language agent (e.g. a translator) has been added to or removed from them.
/// </summary>
public sealed class DetermineEntityLanguagesEvent : EntityEventArgs
[ByRefEvent]
public record struct DetermineEntityLanguagesEvent
{
/// <summary>
/// The default language of this entity. If empty, remain unchanged.
/// This field has no effect if the entity decides to speak in a concrete language.
/// </summary>
public string CurrentLanguage;
/// <summary>
/// The list of all languages the entity may speak. Must NOT be held as a reference!
/// The list of all languages the entity may speak.
/// By default, contains the languages this entity speaks intrinsically.
/// </summary>
public List<string> SpokenLanguages;
public HashSet<string> SpokenLanguages = new();

/// <summary>
/// The list of all languages the entity may understand. Must NOT be held as a reference!
/// The list of all languages the entity may understand.
/// By default, contains the languages this entity understands intrinsically.
/// </summary>
public List<string> UnderstoodLanguages;
public HashSet<string> UnderstoodLanguages = new();

public DetermineEntityLanguagesEvent(string currentLanguage, List<string> spokenLanguages, List<string> understoodLanguages)
{
CurrentLanguage = currentLanguage;
SpokenLanguages = spokenLanguages;
UnderstoodLanguages = understoodLanguages;
}
public DetermineEntityLanguagesEvent() {}
}
39 changes: 29 additions & 10 deletions Content.Server/Language/LanguageSystem.Networking.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
using Content.Server.Language.Events;
using Content.Server.Mind;
using Content.Shared.Language;
using Content.Shared.Language.Components;
using Content.Shared.Language.Events;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Robust.Shared.Player;

namespace Content.Server.Language;

/// <summary>
/// LanguageSystem Networking
/// This is used to update client state when mind change entity.
/// </summary>

public sealed partial class LanguageSystem
{
[Dependency] private readonly MindSystem _mind = default!;


public void InitializeNet()
{
SubscribeNetworkEvent<LanguagesSetMessage>(OnClientSetLanguage);
SubscribeNetworkEvent<RequestLanguagesMessage>((_, session) => SendLanguageStateToClient(session.SenderSession));

SubscribeLocalEvent<LanguageSpeakerComponent, LanguagesUpdateEvent>((uid, comp, _) => SendLanguageStateToClient(uid, comp));

// Refresh the client's state when its mind hops to a different entity
SubscribeLocalEvent<MindContainerComponent, MindAddedMessage>((uid, _, _) => SendLanguageStateToClient(uid));
SubscribeLocalEvent<MindComponent, MindGotRemovedEvent>((_, _, args) =>
{
if (args.Mind.Comp.Session != null)
SendLanguageStateToClient(args.Mind.Comp.Session);
});

SubscribeLocalEvent<LanguageSpeakerComponent, LanguagesUpdateEvent>((uid, comp, _) => SendLanguageStateToClient(uid, comp));
SubscribeNetworkEvent<RequestLanguagesMessage>((_, session) => SendLanguageStateToClient(session.SenderSession));
}


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);
}

private void SendLanguageStateToClient(EntityUid uid, LanguageSpeakerComponent? comp = null)
{
// Try to find a mind inside the entity and notify its session
Expand All @@ -50,10 +61,18 @@ private void SendLanguageStateToClient(ICommonSession session, LanguageSpeakerCo
SendLanguageStateToClient(entity, session, comp);
}

// 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 langs = GetLanguages(uid, component);
var message = new LanguagesUpdatedMessage(langs.CurrentLanguage, langs.SpokenLanguages, langs.UnderstoodLanguages);
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
? new LanguagesUpdatedMessage(UniversalPrototype, [UniversalPrototype], [UniversalPrototype])
: new LanguagesUpdatedMessage(component.CurrentLanguage, component.SpokenLanguages, component.UnderstoodLanguages);

RaiseNetworkEvent(message, session);
}
}
Loading

0 comments on commit 86f0c86

Please sign in to comment.