Files
wwdpublic/Content.Server/Radio/EntitySystems/RadioSystem.cs
Mnemotechnican b15d096a3e Minor Language Fixes (#618)
# Description
Fixes:
- Whisper not undergoing readability obfuscation when out of range
- Handheld translators ignoring language knowledge requirements
- Several animals not having defined languages
- Computers not having languages (this primarily affects the RnD console
and in the future the cargo request console which send radio messages)
- Some languages lacking brightness and thus being hard to read

Also makes language colors from language markers use alpha blending
instead of overriding the original color. The change is subtle, kinda
hard to make it noticable without defeating the original purpose...

<details><summary><h1>Media</h1></summary><p>

Example of the new colors


![image](https://github.com/user-attachments/assets/291c1a6d-829b-43ec-afb7-5c902a1e4aff)

</p></details>

---

# Changelog
🆑
- fix: Whisper can no longer be heard clearly outside the intended
range.
- fix: Translators can no longer be used without knowing the languages
they require.
- fix: Computers (primarily RnD console) now speak GC by default instead
of Universal.
- tweak: Readjusted colors of all languages to make them easier to read.
2024-07-31 16:57:25 -07:00

214 lines
9.2 KiB
C#

using Content.Server.Administration.Logs;
using Content.Server.Chat.Systems;
using Content.Server.Language;
using Content.Server.Power.Components;
using Content.Server.Radio.Components;
using Content.Server.Speech;
using Content.Server.VoiceMask;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Language;
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
using Content.Shared.Speech;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Replays;
using Robust.Shared.Utility;
namespace Content.Server.Radio.EntitySystems;
/// <summary>
/// This system handles intrinsic radios and the general process of converting radio messages into chat messages.
/// </summary>
public sealed class RadioSystem : EntitySystem
{
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly IReplayRecordingManager _replay = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly LanguageSystem _language = default!;
// set used to prevent radio feedback loops.
private readonly HashSet<string> _messages = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
}
private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args)
{
if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
{
SendRadioMessage(uid, args.Message, args.Channel, uid, args.Language);
args.Channel = null; // prevent duplicate messages from other listeners.
}
}
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
{
if (TryComp(uid, out ActorComponent? actor))
{
// Einstein-Engines - languages mechanic
var listener = component.Owner;
var msg = args.OriginalChatMsg;
if (listener != null && !_language.CanUnderstand(listener, args.Language.ID))
msg = args.LanguageObfuscatedChatMsg;
_netMan.ServerSendMessage(new MsgChatMessage { Message = msg}, actor.PlayerSession.Channel);
}
}
/// <summary>
/// Send radio message to all active radio listeners
/// </summary>
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true)
{
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, language: language);
}
/// <summary>
/// Send radio message to all active radio listeners
/// </summary>
/// <param name="messageSource">Entity that spoke the message</param>
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true)
{
if (language == null)
language = _language.GetLanguage(messageSource);
if (!language.SpeechOverride.AllowRadio)
return;
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
if (!_messages.Add(message))
return;
var name = TryComp(messageSource, out VoiceMaskComponent? mask) && mask.Enabled
? mask.VoiceName
: MetaData(messageSource).EntityName;
// Delta-V: Support syrinx voice mask on radio.
if (TryComp(messageSource, out SyrinxVoiceMaskComponent? syrinx) && syrinx.Enabled)
name = syrinx.VoiceName;
name = FormattedMessage.EscapeText(name);
// most radios are relayed to chat, so lets parse the chat message beforehand
SpeechVerbPrototype speech;
if (mask != null
&& mask.Enabled
&& mask.SpeechVerb != null
&& _prototype.TryIndex<SpeechVerbPrototype>(mask.SpeechVerb, out var proto))
{
speech = proto;
}
else
speech = _chat.GetSpeechVerb(messageSource, message);
var content = escapeMarkup
? FormattedMessage.EscapeText(message)
: message;
var wrappedMessage = WrapRadioMessage(messageSource, channel, name, content, language);
var msg = new ChatMessage(ChatChannel.Radio, content, wrappedMessage, NetEntity.Invalid, null);
// ... you guess it
var obfuscated = _language.ObfuscateSpeech(content, language);
var obfuscatedWrapped = WrapRadioMessage(messageSource, channel, name, obfuscated, language);
var notUdsMsg = new ChatMessage(ChatChannel.Radio, obfuscated, obfuscatedWrapped, NetEntity.Invalid, null);
var ev = new RadioReceiveEvent(messageSource, channel, msg, notUdsMsg, language);
var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource);
RaiseLocalEvent(ref sendAttemptEv);
RaiseLocalEvent(radioSource, ref sendAttemptEv);
var canSend = !sendAttemptEv.Cancelled;
var sourceMapId = Transform(radioSource).MapID;
var hasActiveServer = HasActiveServer(sourceMapId, channel.ID);
var hasMicro = HasComp<RadioMicrophoneComponent>(radioSource);
var speakerQuery = GetEntityQuery<RadioSpeakerComponent>();
var radioQuery = EntityQueryEnumerator<ActiveRadioComponent, TransformComponent>();
while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform))
{
if (!radio.ReceiveAllChannels)
{
if (!radio.Channels.Contains(channel.ID) || (TryComp<IntercomComponent>(receiver, out var intercom) &&
!intercom.SupportedChannels.Contains(channel.ID)))
continue;
}
if (!channel.LongRange && transform.MapID != sourceMapId && !radio.GlobalReceive)
continue;
// don't need telecom server for long range channels or handheld radios and intercoms
var needServer = !channel.LongRange && (!hasMicro || !speakerQuery.HasComponent(receiver));
if (needServer && !hasActiveServer)
continue;
// check if message can be sent to specific receiver
var attemptEv = new RadioReceiveAttemptEvent(channel, radioSource, receiver);
RaiseLocalEvent(ref attemptEv);
RaiseLocalEvent(receiver, ref attemptEv);
if (attemptEv.Cancelled)
continue;
// send the message
RaiseLocalEvent(receiver, ref ev);
}
if (name != Name(messageSource))
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} as {name} on {channel.LocalizedName}: {message}");
else
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}");
_replay.RecordServerMessage(msg);
_messages.Remove(message);
}
private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, string name, string message, LanguagePrototype language)
{
// TODO: code duplication with ChatSystem.WrapMessage
var speech = _chat.GetSpeechVerb(source, message);
var languageColor = channel.Color;
if (language.SpeechOverride.Color is { } colorOverride)
languageColor = Color.InterpolateBetween(languageColor, colorOverride, colorOverride.A);
return Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
("color", channel.Color),
("languageColor", languageColor),
("fontType", language.SpeechOverride.FontId ?? speech.FontId),
("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize),
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
("channel", $"\\[{channel.LocalizedName}\\]"),
("name", name),
("message", message));
}
/// <inheritdoc cref="TelecomServerComponent"/>
private bool HasActiveServer(MapId mapId, string channelId)
{
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
foreach (var (_, keys, power, transform) in servers)
{
if (transform.MapID == mapId &&
power.Powered &&
keys.Channels.Contains(channelId))
{
return true;
}
}
return false;
}
}