mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-28 19:17:53 +03:00
# 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  </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.
214 lines
9.2 KiB
C#
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;
|
|
}
|
|
}
|