mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-16 21:17:39 +03:00
Language Refactor 3 (#937)
# Description This significantly improves the quality of the language system by fixing the mistakes I've made almost a year ago while developing it. Mainly, this throws away the old half-broken way of networking in favor of the component state system provided by RT. Language speaker comp is now shared with SendOnlyToOwner = true, and its state is handled manually. In addition to that, this brings the following changes: - UniversalLanguageSpeaker and LanguageKnowledge are now server-side - DetermineLanguagesEvent and LanguagesUpdateEvent are now shared (so that future systems can be built in shared, if needed) - Everything now uses the ProtoId<LanguagePrototype> type instead of raw strings (god, I hated those so much) - The server-side language system now accepts Entity<T?> arguments instead of EntityUid + T - UniversalLanguageSpeaker is now based on DetermineEntityLanguagesEvent and gets an Enabled field, which allows to turn it off. This may have some use in the future. - Some minor cleanup <!-- TODO MEDIA <details><summary><h1>Media</h1></summary> <p>  </p> </details> --> # Changelog No cl --------- Co-authored-by: VMSolidus <evilexecutive@gmail.com>
This commit is contained in:
@@ -1,48 +1,51 @@
|
||||
using Content.Client.Language.Systems;
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Events;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Language;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LanguageMenuWindow : DefaultWindow, IEntityEventSubscriber
|
||||
public sealed partial class LanguageMenuWindow : DefaultWindow
|
||||
{
|
||||
private readonly LanguageSystem _clientLanguageSystem;
|
||||
private readonly List<EntryState> _entries = new();
|
||||
|
||||
|
||||
public LanguageMenuWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_clientLanguageSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<LanguageSystem>();
|
||||
|
||||
_clientLanguageSystem.OnLanguagesChanged += OnUpdateState;
|
||||
_clientLanguageSystem.OnLanguagesChanged += UpdateState;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
_clientLanguageSystem.OnLanguagesChanged -= OnUpdateState;
|
||||
|
||||
if (disposing)
|
||||
_clientLanguageSystem.OnLanguagesChanged -= UpdateState;
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
// Refresh the window when it gets opened.
|
||||
// This actually causes two refreshes: one immediately, and one after the server sends a state message.
|
||||
UpdateState(_clientLanguageSystem.CurrentLanguage, _clientLanguageSystem.SpokenLanguages);
|
||||
_clientLanguageSystem.RequestStateUpdate();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
|
||||
private void OnUpdateState(object? sender, LanguagesUpdatedMessage args)
|
||||
private void UpdateState()
|
||||
{
|
||||
UpdateState(args.CurrentLanguage, args.Spoken);
|
||||
var languageSpeaker = _clientLanguageSystem.GetLocalSpeaker();
|
||||
if (languageSpeaker == null)
|
||||
return;
|
||||
|
||||
UpdateState(languageSpeaker.CurrentLanguage, languageSpeaker.SpokenLanguages);
|
||||
}
|
||||
|
||||
public void UpdateState(string currentLanguage, List<string> spokenLanguages)
|
||||
public void UpdateState(ProtoId<LanguagePrototype> currentLanguage, List<ProtoId<LanguagePrototype>> spokenLanguages)
|
||||
{
|
||||
var langName = Loc.GetString($"language-{currentLanguage}-name");
|
||||
CurrentLanguageLabel.Text = Loc.GetString("language-menu-current-language", ("language", langName));
|
||||
@@ -58,15 +61,15 @@ public sealed partial class LanguageMenuWindow : DefaultWindow, IEntityEventSubs
|
||||
// Disable the button for the currently chosen language
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.button != null)
|
||||
entry.button.Disabled = entry.language == currentLanguage;
|
||||
if (entry.Button != null)
|
||||
entry.Button.Disabled = entry.Language == currentLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddLanguageEntry(string language)
|
||||
private void AddLanguageEntry(ProtoId<LanguagePrototype> language)
|
||||
{
|
||||
var proto = _clientLanguageSystem.GetLanguagePrototype(language);
|
||||
var state = new EntryState { language = language };
|
||||
var state = new EntryState { Language = language };
|
||||
|
||||
var container = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical };
|
||||
|
||||
@@ -87,7 +90,7 @@ public sealed partial class LanguageMenuWindow : DefaultWindow, IEntityEventSubs
|
||||
|
||||
var button = new Button { Text = "Choose" };
|
||||
button.OnPressed += _ => OnLanguageChosen(language);
|
||||
state.button = button;
|
||||
state.Button = button;
|
||||
|
||||
header.AddChild(name);
|
||||
header.AddChild(button);
|
||||
@@ -125,21 +128,18 @@ public sealed partial class LanguageMenuWindow : DefaultWindow, IEntityEventSubs
|
||||
_entries.Add(state);
|
||||
}
|
||||
|
||||
|
||||
private void OnLanguageChosen(string id)
|
||||
private void OnLanguageChosen(ProtoId<LanguagePrototype> id)
|
||||
{
|
||||
var proto = _clientLanguageSystem.GetLanguagePrototype(id);
|
||||
if (proto == null)
|
||||
return;
|
||||
_clientLanguageSystem.RequestSetLanguage(id);
|
||||
|
||||
_clientLanguageSystem.RequestSetLanguage(proto);
|
||||
UpdateState(id, _clientLanguageSystem.SpokenLanguages);
|
||||
// Predict the change
|
||||
if (_clientLanguageSystem.GetLocalSpeaker()?.SpokenLanguages is {} languages)
|
||||
UpdateState(id, languages);
|
||||
}
|
||||
|
||||
|
||||
private struct EntryState
|
||||
{
|
||||
public string language;
|
||||
public Button? button;
|
||||
public ProtoId<LanguagePrototype> Language;
|
||||
public Button? Button;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,61 @@
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Events;
|
||||
using Content.Shared.Language.Systems;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Language.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side language system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unlike the server, the client is not aware of other entities' languages; it's only notified about the entity that it posesses.
|
||||
/// Due to that, this system stores such information in a static manner.
|
||||
/// </remarks>
|
||||
public sealed class LanguageSystem : SharedLanguageSystem
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The current language of the entity currently possessed by the player.
|
||||
/// Invoked when the languages of the local player entity change, for use in UI.
|
||||
/// </summary>
|
||||
public string CurrentLanguage { get; private set; } = default!;
|
||||
/// <summary>
|
||||
/// The list of languages the currently possessed entity can speak.
|
||||
/// </summary>
|
||||
public List<string> SpokenLanguages { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// The list of languages the currently possessed entity can understand.
|
||||
/// </summary>
|
||||
public List<string> UnderstoodLanguages { get; private set; } = new();
|
||||
|
||||
public event EventHandler<LanguagesUpdatedMessage>? OnLanguagesChanged;
|
||||
public event Action? OnLanguagesChanged;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<LanguagesUpdatedMessage>(OnLanguagesUpdated);
|
||||
_client.RunLevelChanged += OnRunLevelChanged;
|
||||
_playerManager.LocalPlayerAttached += NotifyUpdate;
|
||||
SubscribeLocalEvent<LanguageSpeakerComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnLanguagesUpdated(LanguagesUpdatedMessage message)
|
||||
private void OnHandleState(Entity<LanguageSpeakerComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
// TODO this entire thing is horrible. If someone is willing to refactor this, LanguageSpeakerComponent should become shared with SendOnlyToOwner = true
|
||||
// That way, this system will be able to use the existing networking infrastructure instead of relying on this makeshift... whatever this is.
|
||||
CurrentLanguage = message.CurrentLanguage;
|
||||
SpokenLanguages = message.Spoken;
|
||||
UnderstoodLanguages = message.Understood;
|
||||
if (args.Current is not LanguageSpeakerComponent.State state)
|
||||
return;
|
||||
|
||||
OnLanguagesChanged?.Invoke(this, message);
|
||||
}
|
||||
ent.Comp.CurrentLanguage = state.CurrentLanguage;
|
||||
ent.Comp.SpokenLanguages = state.SpokenLanguages;
|
||||
ent.Comp.UnderstoodLanguages = state.UnderstoodLanguages;
|
||||
|
||||
private void OnRunLevelChanged(object? sender, RunLevelChangedEventArgs args)
|
||||
{
|
||||
// Request an update when entering a game
|
||||
if (args.NewLevel == ClientRunLevel.InGame)
|
||||
RequestStateUpdate();
|
||||
if (ent.Owner == _playerManager.LocalEntity)
|
||||
NotifyUpdate(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a network request to the server to update this system's state.
|
||||
/// The server may ignore the said request if the player is not possessing an entity.
|
||||
/// Returns the LanguageSpeakerComponent of the local player entity.
|
||||
/// Will return null if the player does not have an entity, or if the client has not yet received the component state.
|
||||
/// </summary>
|
||||
public void RequestStateUpdate()
|
||||
public LanguageSpeakerComponent? GetLocalSpeaker()
|
||||
{
|
||||
RaiseNetworkEvent(new RequestLanguagesMessage());
|
||||
return CompOrNull<LanguageSpeakerComponent>(_playerManager.LocalEntity);
|
||||
}
|
||||
|
||||
public void RequestSetLanguage(LanguagePrototype language)
|
||||
public void RequestSetLanguage(ProtoId<LanguagePrototype> language)
|
||||
{
|
||||
if (language.ID == CurrentLanguage)
|
||||
if (GetLocalSpeaker()?.CurrentLanguage?.Equals(language) == true)
|
||||
return;
|
||||
|
||||
RaiseNetworkEvent(new LanguagesSetMessage(language.ID));
|
||||
RaiseNetworkEvent(new LanguagesSetMessage(language));
|
||||
}
|
||||
|
||||
// May cause some minor desync...
|
||||
// So to reduce the probability of desync, we replicate the change locally too
|
||||
if (SpokenLanguages.Contains(language.ID))
|
||||
CurrentLanguage = language.ID;
|
||||
private void NotifyUpdate(EntityUid localPlayer)
|
||||
{
|
||||
RaiseLocalEvent(localPlayer, new LanguagesUpdateEvent(), broadcast: true);
|
||||
OnLanguagesChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -21,9 +21,9 @@ using Content.Shared.SSDIndicator;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Language;
|
||||
using Content.Shared.Abilities.Psionics;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
@@ -62,13 +63,13 @@ public sealed class AdminLanguageCommand : ToolshedCommand
|
||||
}
|
||||
|
||||
[CommandImplementation("lsspoken")]
|
||||
public IEnumerable<string> ListSpoken([PipedArgument] EntityUid input)
|
||||
public IEnumerable<ProtoId<LanguagePrototype>> ListSpoken([PipedArgument] EntityUid input)
|
||||
{
|
||||
return Languages.GetSpokenLanguages(input);
|
||||
}
|
||||
|
||||
[CommandImplementation("lsunderstood")]
|
||||
public IEnumerable<string> ListUnderstood([PipedArgument] EntityUid input)
|
||||
public IEnumerable<ProtoId<LanguagePrototype>> ListUnderstood([PipedArgument] EntityUid input)
|
||||
{
|
||||
return Languages.GetUnderstoodLanguages(input);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Components.Translators;
|
||||
using Content.Shared.Language.Systems;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
@@ -107,7 +108,7 @@ public sealed class AdminTranslatorCommand : ToolshedCommand
|
||||
}
|
||||
|
||||
[CommandImplementation("lsspoken")]
|
||||
public IEnumerable<string> ListSpoken([PipedArgument] EntityUid input)
|
||||
public IEnumerable<ProtoId<LanguagePrototype>> ListSpoken([PipedArgument] EntityUid input)
|
||||
{
|
||||
if (!TryGetTranslatorComp(input, out var translator))
|
||||
return [];
|
||||
@@ -115,7 +116,7 @@ public sealed class AdminTranslatorCommand : ToolshedCommand
|
||||
}
|
||||
|
||||
[CommandImplementation("lsunderstood")]
|
||||
public IEnumerable<string> ListUnderstood([PipedArgument] EntityUid input)
|
||||
public IEnumerable<ProtoId<LanguagePrototype>> ListUnderstood([PipedArgument] EntityUid input)
|
||||
{
|
||||
if (!TryGetTranslatorComp(input, out var translator))
|
||||
return [];
|
||||
@@ -123,7 +124,7 @@ public sealed class AdminTranslatorCommand : ToolshedCommand
|
||||
}
|
||||
|
||||
[CommandImplementation("lsrequired")]
|
||||
public IEnumerable<string> ListRequired([PipedArgument] EntityUid input)
|
||||
public IEnumerable<ProtoId<LanguagePrototype>> ListRequired([PipedArgument] EntityUid input)
|
||||
{
|
||||
if (!TryGetTranslatorComp(input, out var translator))
|
||||
return [];
|
||||
|
||||
23
Content.Server/Language/LanguageKnowledgeComponent.cs
Normal file
23
Content.Server/Language/LanguageKnowledgeComponent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Language;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Language;
|
||||
|
||||
/// <summary>
|
||||
/// Stores data about entities' intrinsic language knowledge.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class LanguageKnowledgeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of languages this entity can speak without any external tools.
|
||||
/// </summary>
|
||||
[DataField("speaks", required: true)]
|
||||
public List<ProtoId<LanguagePrototype>> SpokenLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of languages this entity can understand without any external tools.
|
||||
/// </summary>
|
||||
[DataField("understands", required: true)]
|
||||
public List<ProtoId<LanguagePrototype>> UnderstoodLanguages = new();
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
if (!_mind.TryGetMind(uid, out _, out var mindComp) || mindComp.Session == null)
|
||||
return;
|
||||
|
||||
SendLanguageStateToClient(uid, mindComp.Session, comp);
|
||||
}
|
||||
|
||||
private void SendLanguageStateToClient(ICommonSession session, LanguageSpeakerComponent? comp = null)
|
||||
{
|
||||
// Try to find an entity associated with the session and resolve the languages from it
|
||||
if (session.AttachedEntity is not { Valid: true } entity)
|
||||
return;
|
||||
|
||||
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 message = !Resolve(uid, ref component, logMissing: false)
|
||||
? new LanguagesUpdatedMessage(UniversalPrototype, [UniversalPrototype], [UniversalPrototype])
|
||||
: new LanguagesUpdatedMessage(component.CurrentLanguage, component.SpokenLanguages, component.UnderstoodLanguages);
|
||||
|
||||
RaiseNetworkEvent(message, session);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Linq;
|
||||
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;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Language;
|
||||
|
||||
@@ -12,55 +13,85 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeNet();
|
||||
|
||||
SubscribeLocalEvent<LanguageSpeakerComponent, ComponentInit>(OnInitLanguageSpeaker);
|
||||
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, MapInitEvent>(OnUniversalInit);
|
||||
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, ComponentShutdown>(OnUniversalShutdown);
|
||||
SubscribeLocalEvent<LanguageSpeakerComponent, MapInitEvent>(OnInitLanguageSpeaker);
|
||||
SubscribeLocalEvent<LanguageSpeakerComponent, ComponentGetState>(OnGetLanguageState);
|
||||
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, DetermineEntityLanguagesEvent>(OnDetermineUniversalLanguages);
|
||||
SubscribeNetworkEvent<LanguagesSetMessage>(OnClientSetLanguage);
|
||||
|
||||
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, MapInitEvent>((uid, _, _) => UpdateEntityLanguages(uid));
|
||||
SubscribeLocalEvent<UniversalLanguageSpeakerComponent, ComponentRemove>((uid, _, _) => UpdateEntityLanguages(uid));
|
||||
}
|
||||
|
||||
private void OnUniversalShutdown(EntityUid uid, UniversalLanguageSpeakerComponent component, ComponentShutdown args)
|
||||
#region event handling
|
||||
|
||||
private void OnInitLanguageSpeaker(Entity<LanguageSpeakerComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
RemoveLanguage(uid, UniversalPrototype);
|
||||
if (string.IsNullOrEmpty(ent.Comp.CurrentLanguage))
|
||||
ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype);
|
||||
|
||||
UpdateEntityLanguages(ent!);
|
||||
}
|
||||
|
||||
private void OnUniversalInit(EntityUid uid, UniversalLanguageSpeakerComponent component, MapInitEvent args)
|
||||
private void OnGetLanguageState(Entity<LanguageSpeakerComponent> entity, ref ComponentGetState args)
|
||||
{
|
||||
AddLanguage(uid, UniversalPrototype);
|
||||
args.State = new LanguageSpeakerComponent.State
|
||||
{
|
||||
CurrentLanguage = entity.Comp.CurrentLanguage,
|
||||
SpokenLanguages = entity.Comp.SpokenLanguages,
|
||||
UnderstoodLanguages = entity.Comp.UnderstoodLanguages
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDetermineUniversalLanguages(Entity<UniversalLanguageSpeakerComponent> entity, ref DetermineEntityLanguagesEvent ev)
|
||||
{
|
||||
// We only add it as a spoken language; CanUnderstand checks for ULSC itself.
|
||||
if (entity.Comp.Enabled)
|
||||
ev.SpokenLanguages.Add(UniversalPrototype);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region public api
|
||||
|
||||
public bool CanUnderstand(EntityUid listener, string language, LanguageSpeakerComponent? component = null)
|
||||
public bool CanUnderstand(Entity<LanguageSpeakerComponent?> ent, ProtoId<LanguagePrototype> language)
|
||||
{
|
||||
if (language == UniversalPrototype || HasComp<UniversalLanguageSpeakerComponent>(listener))
|
||||
if (language == UniversalPrototype || TryComp<UniversalLanguageSpeakerComponent>(ent, out var uni) && uni.Enabled)
|
||||
return true;
|
||||
|
||||
if (!Resolve(listener, ref component, logMissing: false))
|
||||
return false;
|
||||
|
||||
return component.UnderstoodLanguages.Contains(language);
|
||||
return Resolve(ent, ref ent.Comp, logMissing: false) && ent.Comp.UnderstoodLanguages.Contains(language);
|
||||
}
|
||||
|
||||
public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponent? component = null)
|
||||
public bool CanSpeak(Entity<LanguageSpeakerComponent?> ent, ProtoId<LanguagePrototype> language)
|
||||
{
|
||||
if (HasComp<UniversalLanguageSpeakerComponent>(speaker))
|
||||
return true;
|
||||
|
||||
if (!Resolve(speaker, ref component, logMissing: false))
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
return component.SpokenLanguages.Contains(language);
|
||||
return ent.Comp.SpokenLanguages.Contains(language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current language of the given entity, assumes Universal if it's not a language speaker.
|
||||
/// </summary>
|
||||
public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? component = null)
|
||||
public LanguagePrototype GetLanguage(Entity<LanguageSpeakerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(speaker, ref component, logMissing: false)
|
||||
|| string.IsNullOrEmpty(component.CurrentLanguage)
|
||||
|| !_prototype.TryIndex<LanguagePrototype>(component.CurrentLanguage, out var proto))
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false)
|
||||
|| string.IsNullOrEmpty(ent.Comp.CurrentLanguage)
|
||||
|| !_prototype.TryIndex<LanguagePrototype>(ent.Comp.CurrentLanguage, out var proto)
|
||||
)
|
||||
return Universal;
|
||||
|
||||
return proto;
|
||||
@@ -69,36 +100,31 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
/// <summary>
|
||||
/// Returns the list of languages this entity can speak.
|
||||
/// </summary>
|
||||
/// <remarks>Typically, checking <see cref="LanguageSpeakerComponent.SpokenLanguages"/> is sufficient.</remarks>
|
||||
public List<string> GetSpokenLanguages(EntityUid uid)
|
||||
/// <remarks>This simply returns the value of <see cref="LanguageSpeakerComponent.SpokenLanguages"/>.</remarks>
|
||||
public List<ProtoId<LanguagePrototype>> GetSpokenLanguages(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<LanguageSpeakerComponent>(uid, out var component))
|
||||
return [];
|
||||
|
||||
return component.SpokenLanguages;
|
||||
return TryComp<LanguageSpeakerComponent>(uid, out var component) ? component.SpokenLanguages : [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of languages this entity can understand.
|
||||
/// </summary>
|
||||
/// <remarks>Typically, checking <see cref="LanguageSpeakerComponent.UnderstoodLanguages"/> is sufficient.</remarks>
|
||||
public List<string> GetUnderstoodLanguages(EntityUid uid)
|
||||
/// </summary
|
||||
/// <remarks>This simply returns the value of <see cref="LanguageSpeakerComponent.SpokenLanguages"/>.</remarks>
|
||||
public List<ProtoId<LanguagePrototype>> GetUnderstoodLanguages(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<LanguageSpeakerComponent>(uid, out var component))
|
||||
return [];
|
||||
|
||||
return component.UnderstoodLanguages;
|
||||
return TryComp<LanguageSpeakerComponent>(uid, out var component) ? component.UnderstoodLanguages : [];
|
||||
}
|
||||
|
||||
public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? component = null)
|
||||
public void SetLanguage(Entity<LanguageSpeakerComponent?> ent, ProtoId<LanguagePrototype> language)
|
||||
{
|
||||
if (!CanSpeak(speaker, language)
|
||||
|| !Resolve(speaker, ref component)
|
||||
|| component.CurrentLanguage == language)
|
||||
if (!CanSpeak(ent, language)
|
||||
|| !Resolve(ent, ref ent.Comp)
|
||||
|| ent.Comp.CurrentLanguage == language)
|
||||
return;
|
||||
|
||||
component.CurrentLanguage = language;
|
||||
RaiseLocalEvent(speaker, new LanguagesUpdateEvent(), true);
|
||||
ent.Comp.CurrentLanguage = language;
|
||||
RaiseLocalEvent(ent, new LanguagesUpdateEvent(), true);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,12 +132,12 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
/// </summary>
|
||||
public void AddLanguage(
|
||||
EntityUid uid,
|
||||
string language,
|
||||
ProtoId<LanguagePrototype> language,
|
||||
bool addSpoken = true,
|
||||
bool addUnderstood = true)
|
||||
{
|
||||
EnsureComp<LanguageKnowledgeComponent>(uid, out var knowledge);
|
||||
EnsureComp<LanguageSpeakerComponent>(uid);
|
||||
EnsureComp<LanguageSpeakerComponent>(uid, out var speaker);
|
||||
|
||||
if (addSpoken && !knowledge.SpokenLanguages.Contains(language))
|
||||
knowledge.SpokenLanguages.Add(language);
|
||||
@@ -119,28 +145,29 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
if (addUnderstood && !knowledge.UnderstoodLanguages.Contains(language))
|
||||
knowledge.UnderstoodLanguages.Add(language);
|
||||
|
||||
UpdateEntityLanguages(uid);
|
||||
UpdateEntityLanguages((uid, speaker));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a language from the respective lists of intrinsically known languages of the given entity.
|
||||
/// </summary>
|
||||
public void RemoveLanguage(
|
||||
EntityUid uid,
|
||||
string language,
|
||||
Entity<LanguageKnowledgeComponent?> ent,
|
||||
ProtoId<LanguagePrototype> language,
|
||||
bool removeSpoken = true,
|
||||
bool removeUnderstood = true)
|
||||
{
|
||||
if (!TryComp<LanguageKnowledgeComponent>(uid, out var knowledge))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (removeSpoken)
|
||||
knowledge.SpokenLanguages.Remove(language);
|
||||
ent.Comp.SpokenLanguages.Remove(language);
|
||||
|
||||
if (removeUnderstood)
|
||||
knowledge.UnderstoodLanguages.Remove(language);
|
||||
ent.Comp.UnderstoodLanguages.Remove(language);
|
||||
|
||||
UpdateEntityLanguages(uid);
|
||||
// We don't ensure that the entity has a speaker comp. If it doesn't... Well, woe be the caller of this method.
|
||||
UpdateEntityLanguages(ent.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,15 +175,16 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
/// If not, sets it to the first entry of its SpokenLanguages list, or universal if it's empty.
|
||||
/// </summary>
|
||||
/// <returns>True if the current language was modified, false otherwise.</returns>
|
||||
public bool EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp = null)
|
||||
public bool EnsureValidLanguage(Entity<LanguageSpeakerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(entity, ref comp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!comp.SpokenLanguages.Contains(comp.CurrentLanguage))
|
||||
if (!ent.Comp.SpokenLanguages.Contains(ent.Comp.CurrentLanguage))
|
||||
{
|
||||
comp.CurrentLanguage = comp.SpokenLanguages.FirstOrDefault(UniversalPrototype);
|
||||
RaiseLocalEvent(entity, new LanguagesUpdateEvent());
|
||||
ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype);
|
||||
RaiseLocalEvent(ent, new LanguagesUpdateEvent());
|
||||
Dirty(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -166,14 +194,14 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
/// <summary>
|
||||
/// Immediately refreshes the cached lists of spoken and understood languages for the given entity.
|
||||
/// </summary>
|
||||
public void UpdateEntityLanguages(EntityUid entity)
|
||||
public void UpdateEntityLanguages(Entity<LanguageSpeakerComponent?> ent)
|
||||
{
|
||||
if (!TryComp<LanguageSpeakerComponent>(entity, out var languages))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
var ev = new DetermineEntityLanguagesEvent();
|
||||
// We add the intrinsically known languages first so other systems can manipulate them easily
|
||||
if (TryComp<LanguageKnowledgeComponent>(entity, out var knowledge))
|
||||
if (TryComp<LanguageKnowledgeComponent>(ent, out var knowledge))
|
||||
{
|
||||
foreach (var spoken in knowledge.SpokenLanguages)
|
||||
ev.SpokenLanguages.Add(spoken);
|
||||
@@ -182,28 +210,19 @@ public sealed partial class LanguageSystem : SharedLanguageSystem
|
||||
ev.UnderstoodLanguages.Add(understood);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
languages.SpokenLanguages.Clear();
|
||||
languages.UnderstoodLanguages.Clear();
|
||||
ent.Comp.SpokenLanguages.Clear();
|
||||
ent.Comp.UnderstoodLanguages.Clear();
|
||||
|
||||
languages.SpokenLanguages.AddRange(ev.SpokenLanguages);
|
||||
languages.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages);
|
||||
ent.Comp.SpokenLanguages.AddRange(ev.SpokenLanguages);
|
||||
ent.Comp.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages);
|
||||
|
||||
if (!EnsureValidLanguage(entity))
|
||||
RaiseLocalEvent(entity, new LanguagesUpdateEvent());
|
||||
}
|
||||
// If EnsureValidLanguage returns true, it also raises a LanguagesUpdateEvent, so we try to avoid raising it twice in that case.
|
||||
if (!EnsureValidLanguage(ent))
|
||||
RaiseLocalEvent(ent, new LanguagesUpdateEvent());
|
||||
|
||||
#endregion
|
||||
|
||||
#region event handling
|
||||
|
||||
private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent component, ComponentInit args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(component.CurrentLanguage))
|
||||
component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype);
|
||||
|
||||
UpdateEntityLanguages(uid);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Content.Server.Language.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when its list of languages changes.
|
||||
/// </summary>
|
||||
public sealed class LanguagesUpdateEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Events;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Language;
|
||||
|
||||
@@ -8,7 +8,9 @@ using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Systems;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.Language.Components.Translators;
|
||||
using Content.Shared.Language.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Language;
|
||||
@@ -112,7 +114,7 @@ public sealed class TranslatorSystem : SharedTranslatorSystem
|
||||
|
||||
// Update the current language of the entity if necessary
|
||||
if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {})
|
||||
_language.SetLanguage(holder, firstNewLanguage, languageComp);
|
||||
_language.SetLanguage((holder, languageComp), firstNewLanguage);
|
||||
}
|
||||
|
||||
OnAppearanceChange(translator, translatorComp);
|
||||
@@ -152,7 +154,7 @@ public sealed class TranslatorSystem : SharedTranslatorSystem
|
||||
/// <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)
|
||||
public static bool CheckLanguagesMatch(ICollection<ProtoId<LanguagePrototype>> required, ICollection<ProtoId<LanguagePrototype>> provided, bool requireAll)
|
||||
{
|
||||
if (required.Count == 0)
|
||||
return true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Shared.Language.Components;
|
||||
namespace Content.Server.Language;
|
||||
|
||||
// <summary>
|
||||
// Signifies that this entity can speak and understand any language.
|
||||
@@ -7,5 +7,6 @@ namespace Content.Shared.Language.Components;
|
||||
[RegisterComponent]
|
||||
public sealed partial class UniversalLanguageSpeakerComponent : Component
|
||||
{
|
||||
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Content.Server.Language;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Emoting;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Language;
|
||||
using Content.Shared.Language.Components;
|
||||
using Content.Shared.Language.Systems;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed partial class ForeignerTraitSystem : EntitySystem
|
||||
}
|
||||
|
||||
var alternateLanguage = knowledge.SpokenLanguages.Find(it => it != entity.Comp.BaseLanguage);
|
||||
if (alternateLanguage == null)
|
||||
if (alternateLanguage == default)
|
||||
{
|
||||
Log.Warning($"Entity {entity.Owner} does not have an alternative language to choose from (must have at least one non-GC for ForeignerTrait)!");
|
||||
return;
|
||||
@@ -46,12 +46,12 @@ public sealed partial class ForeignerTraitSystem : EntitySystem
|
||||
|
||||
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);
|
||||
_languages.RemoveLanguage(entity.Owner, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create and give the entity a translator to translator that translates speech between the two specified languages.
|
||||
/// Tries to create and give the entity a translator that translates speech between the two specified languages.
|
||||
/// </summary>
|
||||
public bool TryGiveTranslator(
|
||||
EntityUid uid,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Language.Components;
|
||||
|
||||
// TODO: move to server side, it's never synchronized!
|
||||
|
||||
/// <summary>
|
||||
/// Stores data about entities' intrinsic language knowledge.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class LanguageKnowledgeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// List of languages this entity can speak without any external tools.
|
||||
/// </summary>
|
||||
[DataField("speaks", customTypeSerializer: typeof(PrototypeIdListSerializer<LanguagePrototype>), required: true)]
|
||||
public List<string> SpokenLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of languages this entity can understand without any external tools.
|
||||
/// </summary>
|
||||
[DataField("understands", customTypeSerializer: typeof(PrototypeIdListSerializer<LanguagePrototype>), required: true)]
|
||||
public List<string> UnderstoodLanguages = new();
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace Content.Shared.Language;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
// TODO: either move all language speaker-related components to server side, or make everything else shared.
|
||||
// The current approach leads to confusion, as the server never informs the client of updates in these components.
|
||||
namespace Content.Shared.Language.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the current state of the languages the entity can speak and understand.
|
||||
@@ -10,23 +12,35 @@ namespace Content.Shared.Language;
|
||||
/// All fields of this component are populated during a DetermineEntityLanguagesEvent.
|
||||
/// They are not to be modified externally.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class LanguageSpeakerComponent : Component
|
||||
{
|
||||
public override bool SendOnlyToOwner => true;
|
||||
|
||||
/// <summary>
|
||||
/// The current language the entity uses when speaking.
|
||||
/// Other listeners will hear the entity speak in this language.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string CurrentLanguage = ""; // The language system will override it on init
|
||||
public string CurrentLanguage = ""; // The language system will override it on mapinit
|
||||
|
||||
/// <summary>
|
||||
/// List of languages this entity can speak at the current moment.
|
||||
/// </summary>
|
||||
public List<string> SpokenLanguages = [];
|
||||
[DataField]
|
||||
public List<ProtoId<LanguagePrototype>> SpokenLanguages = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of languages this entity can understand at the current moment.
|
||||
/// </summary>
|
||||
public List<string> UnderstoodLanguages = [];
|
||||
[DataField]
|
||||
public List<ProtoId<LanguagePrototype>> UnderstoodLanguages = [];
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class State : ComponentState
|
||||
{
|
||||
public string CurrentLanguage = default!;
|
||||
public List<ProtoId<LanguagePrototype>> SpokenLanguages = default!;
|
||||
public List<ProtoId<LanguagePrototype>> UnderstoodLanguages = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Language.Components.Translators;
|
||||
@@ -7,21 +8,21 @@ public abstract partial class BaseTranslatorComponent : Component
|
||||
/// <summary>
|
||||
/// The list of additional languages this translator allows the wielder to speak.
|
||||
/// </summary>
|
||||
[DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer<LanguagePrototype>))]
|
||||
public List<string> SpokenLanguages = new();
|
||||
[DataField("spoken")]
|
||||
public List<ProtoId<LanguagePrototype>> SpokenLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of additional languages this translator allows the wielder to understand.
|
||||
/// </summary>
|
||||
[DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer<LanguagePrototype>))]
|
||||
public List<string> UnderstoodLanguages = new();
|
||||
[DataField("understood")]
|
||||
public List<ProtoId<LanguagePrototype>> UnderstoodLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// The languages the wielding MUST know in order for this translator to have effect.
|
||||
/// The field [RequiresAllLanguages] indicates whether all of them are required, or just one.
|
||||
/// </summary>
|
||||
[DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer<LanguagePrototype>))]
|
||||
public List<string> RequiredLanguages = new();
|
||||
[DataField("requires")]
|
||||
public List<ProtoId<LanguagePrototype>> RequiredLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, the wielder must understand all languages in [RequiredLanguages] to speak [SpokenLanguages],
|
||||
@@ -30,9 +31,8 @@ public abstract partial class BaseTranslatorComponent : Component
|
||||
/// Otherwise, at least one language must be known (or the list must be empty).
|
||||
/// </summary>
|
||||
[DataField("requiresAll")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RequiresAllLanguages = false;
|
||||
|
||||
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Content.Shared.Language.Components.Translators;
|
||||
/// A translator that must be held in a hand or a pocket of an entity in order ot have effect.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class HandheldTranslatorComponent : Translators.BaseTranslatorComponent
|
||||
public sealed partial class HandheldTranslatorComponent : BaseTranslatorComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether interacting with this translator toggles it on and off.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.Language;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Language;
|
||||
namespace Content.Shared.Language.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised in order to determine the list of languages the entity can speak and understand at the given moment.
|
||||
@@ -13,13 +13,13 @@ public record struct DetermineEntityLanguagesEvent
|
||||
/// The list of all languages the entity may speak.
|
||||
/// By default, contains the languages this entity speaks intrinsically.
|
||||
/// </summary>
|
||||
public HashSet<string> SpokenLanguages = new();
|
||||
public HashSet<ProtoId<LanguagePrototype>> SpokenLanguages = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of all languages the entity may understand.
|
||||
/// By default, contains the languages this entity understands intrinsically.
|
||||
/// </summary>
|
||||
public HashSet<string> UnderstoodLanguages = new();
|
||||
public HashSet<ProtoId<LanguagePrototype>> UnderstoodLanguages = new();
|
||||
|
||||
public DetermineEntityLanguagesEvent() {}
|
||||
}
|
||||
12
Content.Shared/Language/Events/LanguagesUpdateEvent.cs
Normal file
12
Content.Shared/Language/Events/LanguagesUpdateEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Content.Shared.Language.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when its list of languages changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is raised both on the server and on the client.
|
||||
/// The client raises it broadcast after receiving a new language comp state from the server.
|
||||
/// </remarks>
|
||||
public sealed class LanguagesUpdateEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Language.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Sent to the client when its list of languages changes.
|
||||
/// The client should in turn update its HUD and relevant systems.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class LanguagesUpdatedMessage(string currentLanguage, List<string> spoken, List<string> understood) : EntityEventArgs
|
||||
{
|
||||
public string CurrentLanguage = currentLanguage;
|
||||
public List<string> Spoken = spoken;
|
||||
public List<string> Understood = understood;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Language.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to the server when it needs to learn the list of languages its entity knows.
|
||||
/// This event should always be followed by a <see cref="LanguagesUpdatedMessage"/>, unless the client doesn't have an entity.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RequestLanguagesMessage : EntityEventArgs;
|
||||
@@ -31,9 +31,9 @@ public abstract class SharedLanguageSystem : EntitySystem
|
||||
Universal = _prototype.Index<LanguagePrototype>("Universal");
|
||||
}
|
||||
|
||||
public LanguagePrototype? GetLanguagePrototype(string id)
|
||||
public LanguagePrototype? GetLanguagePrototype(ProtoId<LanguagePrototype> id)
|
||||
{
|
||||
_prototype.TryIndex<LanguagePrototype>(id, out var proto);
|
||||
_prototype.TryIndex(id, out var proto);
|
||||
return proto;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ public abstract class SharedLanguageSystem : EntitySystem
|
||||
public string ObfuscateSpeech(string message, LanguagePrototype language)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var method = language.Obfuscation;
|
||||
method.Obfuscate(builder, message, this);
|
||||
language.Obfuscation.Obfuscate(builder, message, this);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user