From 700611a6bee2716b665fc5d48906875ad2ac7735 Mon Sep 17 00:00:00 2001 From: username <32684670+Snorkcom@users.noreply.github.com> Date: Sun, 16 Aug 2026 04:58:54 +0600 Subject: [PATCH 1/2] Add a guidebook page for communication channels Adds a localized guidebook page under New Player > Controls that lists available radio and collective-mind channels with their chat keys and descriptions. The table is generated from game prototypes, supports filtering, highlights key prefixes, and allows copying non-empty keys by clicking them. --- .../GuideCommunicationChannelsTable.cs | 380 ++++++++++++++++++ .../guidebook/communication-channels.ftl | 50 +++ .../guidebook/communication-channels.ftl | 50 +++ Resources/Prototypes/Guidebook/newplayer.yml | 1 + .../Guidebook/communication_channels.yml | 7 + .../Controls/CommunicationChannels.xml | 7 + 6 files changed, 495 insertions(+) create mode 100644 Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs create mode 100644 Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl create mode 100644 Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl create mode 100644 Resources/Prototypes/_CorvaxGoob/Guidebook/communication_channels.yml create mode 100644 Resources/ServerInfo/_CorvaxGoob/Guidebook/NewPlayer/Controls/CommunicationChannels.xml diff --git a/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs b/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs new file mode 100644 index 000000000000..f64c37e391a0 --- /dev/null +++ b/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Client.Guidebook.Controls; +using Content.Client.Guidebook.Richtext; +using Content.Client.Message; +using Content.Client.UserInterface.ControlExtensions; +using Content.Shared._Starlight.CollectiveMind; +using Content.Shared.Chat; +using Content.Shared.Radio; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client._CorvaxGoob.Guidebook.Controls; + +/// +/// Builds the localized communication-channel reference table used by the guidebook. +/// Radio and collective-mind prototypes are read at runtime, so newly added channels +/// appear here without maintaining a second hard-coded list. +/// +[UsedImplicitly] +public sealed class GuideCommunicationChannelsTable : BoxContainer, IDocumentTag +{ + [Dependency] private IPrototypeManager _prototype = default!; + [Dependency] private IClipboardManager _clipboard = default!; + + private const int NameWidth = 190; + private const int KeyWidth = 80; + private const string LatinKeyColor = "#8fcfff"; + private const string GuideHintColor = "#c8a16c"; + + // Rows are retained so the local search field can hide them without rebuilding the table. + private readonly List _rows = []; + + public GuideCommunicationChannelsTable() + { + IoCManager.InjectDependencies(this); + + Orientation = LayoutOrientation.Vertical; + HorizontalExpand = true; + MouseFilter = MouseFilterMode.Stop; + } + + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) + { + GenerateTable(); + control = this; + return true; + } + + private void GenerateTable() + { + RemoveAllChildren(); + _rows.Clear(); + + // Page copy is built here instead of being hard-coded in the XML document, + // allowing the same guide entry to work with every available locale. + AddChild(BuildPageTitle()); + AddChild(BuildIntroduction()); + AddChild(BuildCopyHint()); + AddChild(BuildSearchBar()); + AddChild(BuildHeaderRow()); + + var rows = _prototype.EnumeratePrototypes() + .Select(BuildRadioRow) + .Concat(_prototype.EnumeratePrototypes().Select(BuildCollectiveMindRow)) + // Sort by the already localized channel name. Keep this simple because + // client content is sandbox-checked and some runtime comparer types are blocked. + .OrderBy(row => row.Name) + .ToList(); + + foreach (var row in rows) + { + var control = new CommunicationChannelGuideRow(row, _clipboard); + _rows.Add(control); + AddChild(control); + } + } + + private static Label BuildPageTitle() + { + return new Label + { + Text = Loc.GetString("guide-communication-channels-page-title"), + StyleClasses = { "LabelHeadingBigger" } + }; + } + + private static RichTextLabel BuildIntroduction() + { + var introduction = new RichTextLabel + { + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 2) + }; + + introduction.SetMarkup(Loc.GetString( + "guide-communication-channels-page-introduction", + ("keyColor", GuideHintColor))); + return introduction; + } + + private static RichTextLabel BuildCopyHint() + { + var hint = new RichTextLabel + { + HorizontalExpand = true + }; + + hint.SetMarkup(Loc.GetString( + "guide-communication-channels-page-copy-hint", + ("keyColor", GuideHintColor))); + return hint; + } + + private LineEdit BuildSearchBar() + { + var search = new LineEdit + { + PlaceHolder = Loc.GetString("guide-communication-channels-search-placeholder"), + HorizontalExpand = true, + Margin = new Thickness(0, 4, 0, 4) + }; + + search.OnTextChanged += _ => ApplyFilter(search.Text); + return search; + } + + private void ApplyFilter(string query) + { + foreach (var row in _rows) + { + row.SetHiddenState(true, query); + } + } + + private static CommunicationChannelGuideData BuildRadioRow(RadioChannelPrototype channel) + { + // Common radio uses ';'. Channels without a key (such as Handheld) intentionally + // leave the table cell empty because there is no prefix the player can type. + var prefix = channel.ID == SharedChatSystem.CommonChannel + ? SharedChatSystem.RadioCommonPrefix.ToString() + : channel.KeyCode == '\0' + ? string.Empty + : $"{SharedChatSystem.RadioChannelPrefix}{char.ToLowerInvariant(channel.KeyCode)}"; + + return new CommunicationChannelGuideData( + channel.LocalizedName, + prefix, + GetDescription("radio", channel.ID, channel.LocalizedName), + channel.Color); + } + + private static CommunicationChannelGuideData BuildCollectiveMindRow(CollectiveMindPrototype mind) + { + var prefix = mind.KeyCode == '\0' + ? string.Empty + : $"{SharedChatSystem.CollectiveMindPrefix}{char.ToLowerInvariant(mind.KeyCode)}"; + + return new CommunicationChannelGuideData( + mind.LocalizedName, + prefix, + GetDescription("collective-mind", mind.ID, mind.LocalizedName), + mind.Color); + } + + private static string GetDescription(string kind, string id, string name) + { + // A channel-specific description is preferred, while the generic text keeps + // mod-added prototypes useful even before a dedicated localization is written. + var specificKey = $"guide-communication-channels-description-{kind}-{id.ToLowerInvariant()}"; + if (Loc.TryGetString(specificKey, out var description)) + return description; + + return Loc.GetString($"guide-communication-channels-description-{kind}-generic", ("channel", name)); + } + + private static Control BuildHeaderRow() + { + var panel = new PanelContainer + { + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 2), + PanelOverride = new StyleBoxFlat + { + BackgroundColor = Color.FromHex("#252735"), + BorderColor = Color.FromHex("#4c5066"), + BorderThickness = new Thickness(1) + } + }; + + var row = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + HorizontalExpand = true, + Margin = new Thickness(6, 4) + }; + + row.AddChild(BuildHeaderLabel("guide-communication-channels-header-name", NameWidth)); + row.AddChild(BuildCenteredHeaderLabel("guide-communication-channels-header-key")); + row.AddChild(BuildHeaderLabel("guide-communication-channels-header-description", 0, true)); + + panel.AddChild(row); + return panel; + } + + private static RichTextLabel BuildHeaderLabel(string locKey, int width, bool expand = false) + { + var label = new RichTextLabel + { + HorizontalExpand = expand + }; + + if (!expand) + label.SetWidth = width; + + label.SetMarkup($"[bold]{FormattedMessage.EscapeText(Loc.GetString(locKey))}[/bold]"); + return label; + } + + private static Control BuildCenteredHeaderLabel(string locKey) + { + var text = FormattedMessage.EscapeText(Loc.GetString(locKey)); + return BuildKeyCell($"[bold]{text}[/bold]"); + } + + /// + /// Creates the fixed-width key cell. Centering a RichTextLabel itself does not + /// center its markup, so a centered child is placed inside this container. + /// + private static BoxContainer BuildKeyCell(string? markup = null, string? copyText = null, IClipboardManager? clipboard = null) + { + var cell = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Align = BoxContainer.AlignMode.Center, + SetWidth = KeyWidth + }; + + if (markup == null) + return cell; + + var label = new RichTextLabel + { + HorizontalAlignment = HAlignment.Center + }; + + label.SetMarkup(markup); + + if (copyText != null && clipboard != null) + { + var button = new ContainerButton + { + HorizontalAlignment = HAlignment.Center + }; + + button.OnPressed += _ => clipboard.SetText(copyText); + button.AddChild(label); + cell.AddChild(button); + return cell; + } + + cell.AddChild(label); + return cell; + } + + private sealed class CommunicationChannelGuideRow : PanelContainer, ISearchableControl + { + private readonly IClipboardManager _clipboard; + private readonly string _searchText; + + public CommunicationChannelGuideRow(CommunicationChannelGuideData data, IClipboardManager clipboard) + { + _clipboard = clipboard; + HorizontalExpand = true; + Margin = new Thickness(0, 0, 0, 2); + PanelOverride = new StyleBoxFlat + { + BackgroundColor = Color.FromHex("#1d1f2a"), + BorderColor = Color.FromHex("#393c4d"), + BorderThickness = new Thickness(1) + }; + + var row = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + HorizontalExpand = true, + Margin = new Thickness(6, 4) + }; + + row.AddChild(BuildChannelName(data.Name, data.Color)); + row.AddChild(BuildKey(data.Prefix)); + row.AddChild(BuildDescription(data.Description)); + + AddChild(row); + + _searchText = $"{data.Prefix} {data.Name} {data.Description}"; + } + + public bool CheckMatchesSearch(string query) + { + return string.IsNullOrWhiteSpace(query) + || _searchText.Contains(query.Trim(), StringComparison.OrdinalIgnoreCase) + || this.ChildrenContainText(query); + } + + public void SetHiddenState(bool state, string query) + { + Visible = CheckMatchesSearch(query) ? state : !state; + } + + private static RichTextLabel BuildDescription(string text) + { + var label = new RichTextLabel + { + HorizontalExpand = true + }; + + label.SetMarkup(FormattedMessage.EscapeText(text)); + return label; + } + + private Control BuildKey(string prefix) + { + if (string.IsNullOrEmpty(prefix)) + return BuildKeyCell(); + + var escapedPrefix = FormattedMessage.EscapeText(prefix); + string markup; + + // Highlight only Latin key letters, leaving ':' or '+' and every + // non-Latin character in the normal text color. + var hasSelectableKey = prefix.Length > 1 && + (prefix[0] == SharedChatSystem.RadioChannelPrefix || + prefix[0] == SharedChatSystem.CollectiveMindPrefix); + + if (hasSelectableKey && IsLatinLetter(prefix[^1])) + { + var marker = FormattedMessage.EscapeText(prefix[..^1]); + var key = FormattedMessage.EscapeText(prefix[^1].ToString()); + markup = $"[bold]{marker}[color={LatinKeyColor}]{key}[/color][/bold]"; + } + else + { + markup = $"[bold]{escapedPrefix}[/bold]"; + } + + return BuildKeyCell(markup, prefix, _clipboard); + } + + private static bool IsLatinLetter(char value) + { + return value is >= 'A' and <= 'Z' or >= 'a' and <= 'z'; + } + + private static RichTextLabel BuildChannelName(string name, Color color) + { + var label = new RichTextLabel + { + // A fixed width keeps all columns aligned and lets long localized + // names such as «Центральное Командование» wrap onto two lines. + SetWidth = NameWidth + }; + + label.SetMarkup($"[color={color.ToHex()}][bold]{FormattedMessage.EscapeText(name)}[/bold][/color]"); + return label; + } + } + + private sealed record CommunicationChannelGuideData( + string Name, + string Prefix, + string Description, + Color Color); +} diff --git a/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl b/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl new file mode 100644 index 000000000000..274e31e17339 --- /dev/null +++ b/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later + +guide-entry-communication-channels = Communication channels + +guide-communication-channels-page-title = Communication channels +guide-communication-channels-page-introduction = The table lists channels that can be selected with a prefix at the start of a message. Radio channels require access through a suitable device, while mind channels require the corresponding mental link. ([color={ $keyColor }];[/color]) sends to the common channel, ([color={ $keyColor }]:[/color]) selects a radio channel by its key, and ([color={ $keyColor }]+[/color]) selects a collective-mind channel. +guide-communication-channels-page-copy-hint = [color={ $keyColor }]Click a key to copy it.[/color] + +guide-communication-channels-search-placeholder = Filter channels +guide-communication-channels-header-key = Key +guide-communication-channels-header-name = Channel +guide-communication-channels-header-description = Description + +guide-communication-channels-description-radio-generic = The “{ $channel }” radio channel. Sending requires access through equipment or an intrinsic transmitter. +guide-communication-channels-description-collective-mind-generic = The “{ $channel }” channel for entities connected to the matching collective mind. + +guide-communication-channels-description-radio-common = The station-wide common channel. Most headsets can use it, and it is sent with a semicolon. +guide-communication-channels-description-radio-centcom = Channel for Central Command representatives and special response teams. +guide-communication-channels-description-radio-command = Command channel for the captain, department heads, and other command roles. +guide-communication-channels-description-radio-engineering = Engineering channel for repairs, construction, atmospherics, and power supply. +guide-communication-channels-description-radio-medical = Medical channel for doctors, chemists, paramedics, and sharing medical information. +guide-communication-channels-description-radio-science = Science channel for scientists, researchers, roboticists, anomalists, and experiment coordination. +guide-communication-channels-description-radio-security = Security channel for patrols, call response, and department coordination. +guide-communication-channels-description-radio-service = Service channel for the bar, kitchen, botany, janitorial work, chapel, theater, and other service tasks. +guide-communication-channels-description-radio-supply = Supply channel for cargo technicians, miners, salvage specialists, and station orders. +guide-communication-channels-description-radio-syndicate = Hidden Syndicate channel. Access comes from matching devices and encryption keys. +guide-communication-channels-description-radio-handheld = Local handheld-radio channel. Communication requires a handheld radio tuned to the same frequency. +guide-communication-channels-description-radio-freelance = Freelance channel for independent groups when their equipment grants access. +guide-communication-channels-description-radio-xenoborg = Xenoborg channel for xenoborgs and related systems. +guide-communication-channels-description-radio-mothership = Mothership core channel for communicating with xenoborgs. Xenoborgs can hear it, but transmit on the Xenoborg channel. +guide-communication-channels-description-radio-legal = Legal channel for the magistrate, lawyers, and related legal roles. +guide-communication-channels-description-radio-future = Special long-range future radio channel. +guide-communication-channels-description-radio-radioshow = Broadcasting channel for the radio host and station radio. +guide-communication-channels-description-radio-cosmicradio = Cosmic cult channel for cultists and related entities. + +guide-communication-channels-description-collective-mind-dragonmind = Dragon mind channel for carp, sharks, and related entities. +guide-communication-channels-description-collective-mind-lingmind = Changeling hivemind channel. +guide-communication-channels-description-collective-mind-tidemind = Tidemind channel for greytide-related events. +guide-communication-channels-description-collective-mind-blobmind = Blob mind channel for the core, factories, and other blob organisms. +guide-communication-channels-description-collective-mind-mansuslink = Mansus Link for heretics and their summoned creatures. +guide-communication-channels-description-collective-mind-abductormind = Mind channel for greys and their team. +guide-communication-channels-description-collective-mind-binary = Binary channel for synthetics and related devices. +guide-communication-channels-description-collective-mind-dronemind = Drone mind channel. +guide-communication-channels-description-collective-mind-mousemind = Mouse mind channel. +guide-communication-channels-description-collective-mind-empathy = Empathic link for entities that have access to this mind. +guide-communication-channels-description-collective-mind-binglemind = Bingle link. +guide-communication-channels-description-collective-mind-shadowmind = Shadow mind channel. +guide-communication-channels-description-collective-mind-xenomorphivemind = Xenomorph hivemind channel. +guide-communication-channels-description-collective-mind-abductormindantag = Antagonist abductor mind channel. +guide-communication-channels-description-collective-mind-wraithmind = Wraith mind channel. diff --git a/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl b/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl new file mode 100644 index 000000000000..f2c6446a87b0 --- /dev/null +++ b/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later + +guide-entry-communication-channels = Каналы связи + +guide-communication-channels-page-title = Каналы связи +guide-communication-channels-page-introduction = В таблице перечислены каналы, которые можно выбрать префиксом в начале сообщения. Для радиоканала нужен доступ через подходящее устройство, а для канала разума — соответствующая связь. ([color={ $keyColor }];[/color]) отправляет в общий канал, ([color={ $keyColor }]:[/color]) выбирает радиоканал по букве, а ([color={ $keyColor }]+[/color]) выбирает канал коллективного разума. +guide-communication-channels-page-copy-hint = [color={ $keyColor }]Нажмите на ключ, чтобы скопировать его.[/color] + +guide-communication-channels-search-placeholder = Фильтр каналов +guide-communication-channels-header-key = Ключ +guide-communication-channels-header-name = Канал +guide-communication-channels-header-description = Описание + +guide-communication-channels-description-radio-generic = Радиоканал «{ $channel }». Для отправки нужен доступ к этому каналу через экипировку или встроенный передатчик. +guide-communication-channels-description-collective-mind-generic = Канал «{ $channel }» для существ, подключённых к соответствующему коллективному разуму. + +guide-communication-channels-description-radio-common = Общий канал станции. Обычно доступен большинству гарнитур и отправляется через точку с запятой. +guide-communication-channels-description-radio-centcom = Канал для связи с представителями Центрального Командования и специальными группами. +guide-communication-channels-description-radio-command = Командный канал капитана, глав отделов и других должностей командования. +guide-communication-channels-description-radio-engineering = Канал инженерного отдела для ремонта, строительства, атмосферики и электроснабжения. +guide-communication-channels-description-radio-medical = Канал медицинского отдела для врачей, химиков, парамедиков и обмена медицинской информацией. +guide-communication-channels-description-radio-science = Канал научного отдела для учёных, исследователей, робототехников, аномалистов и координации экспериментов. +guide-communication-channels-description-radio-security = Канал службы безопасности для патрулей, реагирования на вызовы и координации отдела. +guide-communication-channels-description-radio-service = Канал сервисного отдела: бар, кухня, ботаника, уборка, церковь, театр и прочие сервисные задачи. +guide-communication-channels-description-radio-supply = Канал отдела снабжения для грузчиков, шахтёров, утилизаторов и заказов. +guide-communication-channels-description-radio-syndicate = Скрытый канал Синдиката. Доступен через соответствующие устройства и ключи. +guide-communication-channels-description-radio-handheld = Локальный канал портативных раций. Для связи требуется портативная рация, настроенная на ту же частоту. +guide-communication-channels-description-radio-freelance = Канал фрилансеров и независимых групп, если экипировка даёт к нему доступ. +guide-communication-channels-description-radio-xenoborg = Канал ксеноборгов и связанных с ними систем. +guide-communication-channels-description-radio-mothership = Канал ядра материнского шаттла для связи с ксеноборгами. Ксеноборги слышат его, но сами передают по каналу «Ксеноборги». +guide-communication-channels-description-radio-legal = Юридический канал для магистрата, юристов и связанных правовых ролей. +guide-communication-channels-description-radio-future = Специальный дальний радиоканал будущего. +guide-communication-channels-description-radio-radioshow = Канал радиовещания для ведущего и станционной радиостанции. +guide-communication-channels-description-radio-cosmicradio = Канал космического культа и связанных с ним существ. + +guide-communication-channels-description-collective-mind-dragonmind = Канал разума дракона для карпов, акул и связанных с ним существ. +guide-communication-channels-description-collective-mind-lingmind = Канал коллективного разума генокрадов. +guide-communication-channels-description-collective-mind-tidemind = Канал грейтайдов и связанных с ними событий. +guide-communication-channels-description-collective-mind-blobmind = Канал блоба для ядра, фабрик и других частей организма. +guide-communication-channels-description-collective-mind-mansuslink = Связь Мансуса для еретиков и их призванных существ. +guide-communication-channels-description-collective-mind-abductormind = Канал серых и их команды. +guide-communication-channels-description-collective-mind-binary = Двоичный канал синтетиков и связанных устройств. +guide-communication-channels-description-collective-mind-dronemind = Канал дронов. +guide-communication-channels-description-collective-mind-mousemind = Канал мышей. +guide-communication-channels-description-collective-mind-empathy = Эмпатическая связь существ, которым доступен этот разум. +guide-communication-channels-description-collective-mind-binglemind = Связь бинглов. +guide-communication-channels-description-collective-mind-shadowmind = Разум теней. +guide-communication-channels-description-collective-mind-xenomorphivemind = Разум улья ксеноморфов. +guide-communication-channels-description-collective-mind-abductormindantag = Канал антагонистов-абдукторов. +guide-communication-channels-description-collective-mind-wraithmind = Разум фантомов. diff --git a/Resources/Prototypes/Guidebook/newplayer.yml b/Resources/Prototypes/Guidebook/newplayer.yml index b268ab895d87..a2559d00f016 100644 --- a/Resources/Prototypes/Guidebook/newplayer.yml +++ b/Resources/Prototypes/Guidebook/newplayer.yml @@ -14,6 +14,7 @@ text: "/ServerInfo/Guidebook/NewPlayer/Controls/Controls.xml" children: - Radio + - CommunicationChannels # CorvaxGoob - available-radio-channels-guide - type: guideEntry id: Radio diff --git a/Resources/Prototypes/_CorvaxGoob/Guidebook/communication_channels.yml b/Resources/Prototypes/_CorvaxGoob/Guidebook/communication_channels.yml new file mode 100644 index 000000000000..c1f34b2647c6 --- /dev/null +++ b/Resources/Prototypes/_CorvaxGoob/Guidebook/communication_channels.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later + +- type: guideEntry + id: CommunicationChannels + name: guide-entry-communication-channels + text: "/ServerInfo/_CorvaxGoob/Guidebook/NewPlayer/Controls/CommunicationChannels.xml" + priority: 1 diff --git a/Resources/ServerInfo/_CorvaxGoob/Guidebook/NewPlayer/Controls/CommunicationChannels.xml b/Resources/ServerInfo/_CorvaxGoob/Guidebook/NewPlayer/Controls/CommunicationChannels.xml new file mode 100644 index 000000000000..1f97e73405de --- /dev/null +++ b/Resources/ServerInfo/_CorvaxGoob/Guidebook/NewPlayer/Controls/CommunicationChannels.xml @@ -0,0 +1,7 @@ + + + + + From 2b4f2a5d60d93418ceb2ef7a43c5dfabdf4350a3 Mon Sep 17 00:00:00 2001 From: username <32684670+Snorkcom@users.noreply.github.com> Date: Sun, 16 Aug 2026 17:52:36 +0600 Subject: [PATCH 2/2] refactor + fixes+ comments --- .../GuideCommunicationChannelsTable.cs | 153 +++++++++--------- .../guidebook/communication-channels.ftl | 28 ++-- .../guidebook/communication-channels.ftl | 28 ++-- 3 files changed, 111 insertions(+), 98 deletions(-) diff --git a/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs b/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs index f64c37e391a0..d36bf950e5c1 100644 --- a/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs +++ b/Content.Client/_CorvaxGoob/Guidebook/Controls/GuideCommunicationChannelsTable.cs @@ -5,7 +5,6 @@ using Content.Client.Guidebook.Controls; using Content.Client.Guidebook.Richtext; using Content.Client.Message; -using Content.Client.UserInterface.ControlExtensions; using Content.Shared._Starlight.CollectiveMind; using Content.Shared.Chat; using Content.Shared.Radio; @@ -31,9 +30,22 @@ public sealed class GuideCommunicationChannelsTable : BoxContainer, IDocumentTag private const int NameWidth = 190; private const int KeyWidth = 80; - private const string LatinKeyColor = "#8fcfff"; private const string GuideHintColor = "#c8a16c"; + private static readonly StyleBoxFlat HeaderPanelStyle = new() + { + BackgroundColor = Color.FromHex("#252735"), + BorderColor = Color.FromHex("#4c5066"), + BorderThickness = new Thickness(1) + }; + + private static readonly StyleBoxFlat RowPanelStyle = new() + { + BackgroundColor = Color.FromHex("#1d1f2a"), + BorderColor = Color.FromHex("#393c4d"), + BorderThickness = new Thickness(1) + }; + // Rows are retained so the local search field can hide them without rebuilding the table. private readonly List _rows = []; @@ -58,21 +70,18 @@ private void GenerateTable() RemoveAllChildren(); _rows.Clear(); - // Page copy is built here instead of being hard-coded in the XML document, - // allowing the same guide entry to work with every available locale. + // Page layout: title, introduction, copy hint, filter, header, then generated rows. AddChild(BuildPageTitle()); AddChild(BuildIntroduction()); AddChild(BuildCopyHint()); AddChild(BuildSearchBar()); AddChild(BuildHeaderRow()); + // Sort by the already localized channel name. var rows = _prototype.EnumeratePrototypes() .Select(BuildRadioRow) .Concat(_prototype.EnumeratePrototypes().Select(BuildCollectiveMindRow)) - // Sort by the already localized channel name. Keep this simple because - // client content is sandbox-checked and some runtime comparer types are blocked. - .OrderBy(row => row.Name) - .ToList(); + .OrderBy(static row => row.Name); foreach (var row in rows) { @@ -86,36 +95,34 @@ private static Label BuildPageTitle() { return new Label { - Text = Loc.GetString("guide-communication-channels-page-title"), + Text = Loc.GetString("guide-entry-communication-channels"), StyleClasses = { "LabelHeadingBigger" } }; } private static RichTextLabel BuildIntroduction() { - var introduction = new RichTextLabel - { - HorizontalExpand = true, - Margin = new Thickness(0, 2, 0, 2) - }; - - introduction.SetMarkup(Loc.GetString( + return BuildPageText( "guide-communication-channels-page-introduction", - ("keyColor", GuideHintColor))); - return introduction; + new Thickness(0, 2, 0, 2)); } private static RichTextLabel BuildCopyHint() { - var hint = new RichTextLabel + return BuildPageText("guide-communication-channels-page-copy-hint"); + } + + // Creates localized rich-text blocks shown above the channel table. + private static RichTextLabel BuildPageText(string locKey, Thickness margin = default) + { + var label = new RichTextLabel { - HorizontalExpand = true + HorizontalExpand = true, + Margin = margin }; - hint.SetMarkup(Loc.GetString( - "guide-communication-channels-page-copy-hint", - ("keyColor", GuideHintColor))); - return hint; + label.SetMarkup(Loc.GetString(locKey, ("keyColor", GuideHintColor))); + return label; } private LineEdit BuildSearchBar() @@ -127,7 +134,7 @@ private LineEdit BuildSearchBar() Margin = new Thickness(0, 4, 0, 4) }; - search.OnTextChanged += _ => ApplyFilter(search.Text); + search.OnTextChanged += args => ApplyFilter(args.Text); return search; } @@ -139,40 +146,49 @@ private void ApplyFilter(string query) } } + // Builds a row from the localized name, keycode, and color of a radioChannel prototype. private static CommunicationChannelGuideData BuildRadioRow(RadioChannelPrototype channel) { - // Common radio uses ';'. Channels without a key (such as Handheld) intentionally - // leave the table cell empty because there is no prefix the player can type. + // Common uses the chat-wide ';' prefix. Other channels combine the ':' prefix + // with RadioChannelPrototype.KeyCode, which is loaded from the prototype's keycode field. + var name = channel.LocalizedName; var prefix = channel.ID == SharedChatSystem.CommonChannel ? SharedChatSystem.RadioCommonPrefix.ToString() - : channel.KeyCode == '\0' - ? string.Empty - : $"{SharedChatSystem.RadioChannelPrefix}{char.ToLowerInvariant(channel.KeyCode)}"; + : BuildPrefix(SharedChatSystem.RadioChannelPrefix, channel.KeyCode); return new CommunicationChannelGuideData( - channel.LocalizedName, + name, prefix, - GetDescription("radio", channel.ID, channel.LocalizedName), + GetDescription("radio", channel.ID, name), channel.Color); } + // Builds a row from the localized name, keycode, and color of a collectiveMind prototype. private static CommunicationChannelGuideData BuildCollectiveMindRow(CollectiveMindPrototype mind) { - var prefix = mind.KeyCode == '\0' - ? string.Empty - : $"{SharedChatSystem.CollectiveMindPrefix}{char.ToLowerInvariant(mind.KeyCode)}"; + // The '+' prefix comes from the chat system, while CollectiveMindPrototype.KeyCode + // is loaded from the prototype's keycode field. + var name = mind.LocalizedName; + var prefix = BuildPrefix(SharedChatSystem.CollectiveMindPrefix, mind.KeyCode); return new CommunicationChannelGuideData( - mind.LocalizedName, + name, prefix, - GetDescription("collective-mind", mind.ID, mind.LocalizedName), + GetDescription("collective-mind", mind.ID, name), mind.Color); } + // Returns the exact prefix players type in chat, or no key for keyless prototypes. + private static string BuildPrefix(char prefix, char keyCode) + { + return keyCode == '\0' + ? string.Empty + : $"{prefix}{char.ToLowerInvariant(keyCode)}"; + } + + // Uses a specific description when present, otherwise falls back to generic text. private static string GetDescription(string kind, string id, string name) { - // A channel-specific description is preferred, while the generic text keeps - // mod-added prototypes useful even before a dedicated localization is written. var specificKey = $"guide-communication-channels-description-{kind}-{id.ToLowerInvariant()}"; if (Loc.TryGetString(specificKey, out var description)) return description; @@ -180,18 +196,13 @@ private static string GetDescription(string kind, string id, string name) return Loc.GetString($"guide-communication-channels-description-{kind}-generic", ("channel", name)); } - private static Control BuildHeaderRow() + private static PanelContainer BuildHeaderRow() { var panel = new PanelContainer { HorizontalExpand = true, Margin = new Thickness(0, 2, 0, 2), - PanelOverride = new StyleBoxFlat - { - BackgroundColor = Color.FromHex("#252735"), - BorderColor = Color.FromHex("#4c5066"), - BorderThickness = new Thickness(1) - } + PanelOverride = HeaderPanelStyle }; var row = new BoxContainer @@ -203,27 +214,27 @@ private static Control BuildHeaderRow() row.AddChild(BuildHeaderLabel("guide-communication-channels-header-name", NameWidth)); row.AddChild(BuildCenteredHeaderLabel("guide-communication-channels-header-key")); - row.AddChild(BuildHeaderLabel("guide-communication-channels-header-description", 0, true)); + row.AddChild(BuildHeaderLabel("guide-communication-channels-header-description")); panel.AddChild(row); return panel; } - private static RichTextLabel BuildHeaderLabel(string locKey, int width, bool expand = false) + private static RichTextLabel BuildHeaderLabel(string locKey, int? width = null) { var label = new RichTextLabel { - HorizontalExpand = expand + HorizontalExpand = width == null }; - if (!expand) - label.SetWidth = width; + if (width != null) + label.SetWidth = width.Value; label.SetMarkup($"[bold]{FormattedMessage.EscapeText(Loc.GetString(locKey))}[/bold]"); return label; } - private static Control BuildCenteredHeaderLabel(string locKey) + private static BoxContainer BuildCenteredHeaderLabel(string locKey) { var text = FormattedMessage.EscapeText(Loc.GetString(locKey)); return BuildKeyCell($"[bold]{text}[/bold]"); @@ -233,7 +244,10 @@ private static Control BuildCenteredHeaderLabel(string locKey) /// Creates the fixed-width key cell. Centering a RichTextLabel itself does not /// center its markup, so a centered child is placed inside this container. /// - private static BoxContainer BuildKeyCell(string? markup = null, string? copyText = null, IClipboardManager? clipboard = null) + private static BoxContainer BuildKeyCell( + string? markup = null, + string? copyText = null, + IClipboardManager? clipboard = null) { var cell = new BoxContainer { @@ -269,22 +283,17 @@ private static BoxContainer BuildKeyCell(string? markup = null, string? copyText return cell; } + // Renders one channel row, stores searchable text for the filter, + // and lets players copy a non-empty chat key by clicking it. private sealed class CommunicationChannelGuideRow : PanelContainer, ISearchableControl { - private readonly IClipboardManager _clipboard; private readonly string _searchText; public CommunicationChannelGuideRow(CommunicationChannelGuideData data, IClipboardManager clipboard) { - _clipboard = clipboard; HorizontalExpand = true; Margin = new Thickness(0, 0, 0, 2); - PanelOverride = new StyleBoxFlat - { - BackgroundColor = Color.FromHex("#1d1f2a"), - BorderColor = Color.FromHex("#393c4d"), - BorderThickness = new Thickness(1) - }; + PanelOverride = RowPanelStyle; var row = new BoxContainer { @@ -294,7 +303,7 @@ public CommunicationChannelGuideRow(CommunicationChannelGuideData data, IClipboa }; row.AddChild(BuildChannelName(data.Name, data.Color)); - row.AddChild(BuildKey(data.Prefix)); + row.AddChild(BuildKey(data.Prefix, clipboard)); row.AddChild(BuildDescription(data.Description)); AddChild(row); @@ -304,9 +313,9 @@ public CommunicationChannelGuideRow(CommunicationChannelGuideData data, IClipboa public bool CheckMatchesSearch(string query) { - return string.IsNullOrWhiteSpace(query) - || _searchText.Contains(query.Trim(), StringComparison.OrdinalIgnoreCase) - || this.ChildrenContainText(query); + var search = query.Trim(); + return search.Length == 0 + || _searchText.Contains(search, StringComparison.OrdinalIgnoreCase); } public void SetHiddenState(bool state, string query) @@ -325,12 +334,12 @@ private static RichTextLabel BuildDescription(string text) return label; } - private Control BuildKey(string prefix) + // Builds the key cell, adds color highlighting, and wires click-to-copy. + private static BoxContainer BuildKey(string prefix, IClipboardManager clipboard) { if (string.IsNullOrEmpty(prefix)) return BuildKeyCell(); - var escapedPrefix = FormattedMessage.EscapeText(prefix); string markup; // Highlight only Latin key letters, leaving ':' or '+' and every @@ -343,14 +352,14 @@ private Control BuildKey(string prefix) { var marker = FormattedMessage.EscapeText(prefix[..^1]); var key = FormattedMessage.EscapeText(prefix[^1].ToString()); - markup = $"[bold]{marker}[color={LatinKeyColor}]{key}[/color][/bold]"; + markup = $"[bold]{marker}[color={GuideHintColor}]{key}[/color][/bold]"; } else { - markup = $"[bold]{escapedPrefix}[/bold]"; + markup = $"[bold]{FormattedMessage.EscapeText(prefix)}[/bold]"; } - return BuildKeyCell(markup, prefix, _clipboard); + return BuildKeyCell(markup, prefix, clipboard); } private static bool IsLatinLetter(char value) @@ -362,8 +371,7 @@ private static RichTextLabel BuildChannelName(string name, Color color) { var label = new RichTextLabel { - // A fixed width keeps all columns aligned and lets long localized - // names such as «Центральное Командование» wrap onto two lines. + // A fixed width keeps columns aligned and lets long names wrap. SetWidth = NameWidth }; @@ -372,6 +380,7 @@ private static RichTextLabel BuildChannelName(string name, Color color) } } + // Prepared data used to render one channel table row. private sealed record CommunicationChannelGuideData( string Name, string Prefix, diff --git a/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl b/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl index 274e31e17339..e5c88ef42cd1 100644 --- a/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl +++ b/Resources/Locale/en-US/_CorvaxGoob/guidebook/communication-channels.ftl @@ -2,11 +2,13 @@ guide-entry-communication-channels = Communication channels -guide-communication-channels-page-title = Communication channels -guide-communication-channels-page-introduction = The table lists channels that can be selected with a prefix at the start of a message. Radio channels require access through a suitable device, while mind channels require the corresponding mental link. ([color={ $keyColor }];[/color]) sends to the common channel, ([color={ $keyColor }]:[/color]) selects a radio channel by its key, and ([color={ $keyColor }]+[/color]) selects a collective-mind channel. -guide-communication-channels-page-copy-hint = [color={ $keyColor }]Click a key to copy it.[/color] +guide-communication-channels-page-introduction = + The table lists channels that can be selected with a prefix at the start of a message. + Radio channels require access through a suitable device, while mind channels require the corresponding link. + ([color={ $keyColor }];[/color]) sends to the common channel, ([color={ $keyColor }]:[/color]) selects a radio channel by its key, and ([color={ $keyColor }]+[/color]) selects a collective-mind channel. +guide-communication-channels-page-copy-hint = [color={ $keyColor }]Click a key in the table to copy it.[/color] -guide-communication-channels-search-placeholder = Filter channels +guide-communication-channels-search-placeholder = Filter guide-communication-channels-header-key = Key guide-communication-channels-header-name = Channel guide-communication-channels-header-description = Description @@ -17,29 +19,29 @@ guide-communication-channels-description-collective-mind-generic = The “{ $cha guide-communication-channels-description-radio-common = The station-wide common channel. Most headsets can use it, and it is sent with a semicolon. guide-communication-channels-description-radio-centcom = Channel for Central Command representatives and special response teams. guide-communication-channels-description-radio-command = Command channel for the captain, department heads, and other command roles. -guide-communication-channels-description-radio-engineering = Engineering channel for repairs, construction, atmospherics, and power supply. +guide-communication-channels-description-radio-engineering = Engineering channel for repairs, construction, power supply, and atmospherics. guide-communication-channels-description-radio-medical = Medical channel for doctors, chemists, paramedics, and sharing medical information. -guide-communication-channels-description-radio-science = Science channel for scientists, researchers, roboticists, anomalists, and experiment coordination. +guide-communication-channels-description-radio-science = Science channel for scientists, roboticists, anomalists, and experiment coordination. guide-communication-channels-description-radio-security = Security channel for patrols, call response, and department coordination. -guide-communication-channels-description-radio-service = Service channel for the bar, kitchen, botany, janitorial work, chapel, theater, and other service tasks. +guide-communication-channels-description-radio-service = Service channel for the bar, kitchen, botany, janitorial work, theater, chapel, and other service tasks. guide-communication-channels-description-radio-supply = Supply channel for cargo technicians, miners, salvage specialists, and station orders. -guide-communication-channels-description-radio-syndicate = Hidden Syndicate channel. Access comes from matching devices and encryption keys. +guide-communication-channels-description-radio-syndicate = Hidden Syndicate channel. Access comes from matching devices and encryption keys; transmissions are encrypted, hiding speaker names. guide-communication-channels-description-radio-handheld = Local handheld-radio channel. Communication requires a handheld radio tuned to the same frequency. guide-communication-channels-description-radio-freelance = Freelance channel for independent groups when their equipment grants access. guide-communication-channels-description-radio-xenoborg = Xenoborg channel for xenoborgs and related systems. guide-communication-channels-description-radio-mothership = Mothership core channel for communicating with xenoborgs. Xenoborgs can hear it, but transmit on the Xenoborg channel. guide-communication-channels-description-radio-legal = Legal channel for the magistrate, lawyers, and related legal roles. -guide-communication-channels-description-radio-future = Special long-range future radio channel. +guide-communication-channels-description-radio-future = Special long-range future radio channel. Will be available in the future… guide-communication-channels-description-radio-radioshow = Broadcasting channel for the radio host and station radio. guide-communication-channels-description-radio-cosmicradio = Cosmic cult channel for cultists and related entities. -guide-communication-channels-description-collective-mind-dragonmind = Dragon mind channel for carp, sharks, and related entities. +guide-communication-channels-description-collective-mind-dragonmind = Collective mind channel for the dragon, carp, and sharks. guide-communication-channels-description-collective-mind-lingmind = Changeling hivemind channel. guide-communication-channels-description-collective-mind-tidemind = Tidemind channel for greytide-related events. -guide-communication-channels-description-collective-mind-blobmind = Blob mind channel for the core, factories, and other blob organisms. +guide-communication-channels-description-collective-mind-blobmind = Blob channel linking the core, minions, and other parts of the organism. guide-communication-channels-description-collective-mind-mansuslink = Mansus Link for heretics and their summoned creatures. -guide-communication-channels-description-collective-mind-abductormind = Mind channel for greys and their team. -guide-communication-channels-description-collective-mind-binary = Binary channel for synthetics and related devices. +guide-communication-channels-description-collective-mind-abductormind = Communication channel for greys and their team. +guide-communication-channels-description-collective-mind-binary = Binary channel for synthetics, cyborgs, borgs, artificial intelligence, and related devices. guide-communication-channels-description-collective-mind-dronemind = Drone mind channel. guide-communication-channels-description-collective-mind-mousemind = Mouse mind channel. guide-communication-channels-description-collective-mind-empathy = Empathic link for entities that have access to this mind. diff --git a/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl b/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl index f2c6446a87b0..10bbe84c4d71 100644 --- a/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl +++ b/Resources/Locale/ru-RU/_CorvaxGoob/guidebook/communication-channels.ftl @@ -2,11 +2,13 @@ guide-entry-communication-channels = Каналы связи -guide-communication-channels-page-title = Каналы связи -guide-communication-channels-page-introduction = В таблице перечислены каналы, которые можно выбрать префиксом в начале сообщения. Для радиоканала нужен доступ через подходящее устройство, а для канала разума — соответствующая связь. ([color={ $keyColor }];[/color]) отправляет в общий канал, ([color={ $keyColor }]:[/color]) выбирает радиоканал по букве, а ([color={ $keyColor }]+[/color]) выбирает канал коллективного разума. -guide-communication-channels-page-copy-hint = [color={ $keyColor }]Нажмите на ключ, чтобы скопировать его.[/color] +guide-communication-channels-page-introduction = + В таблице перечислены каналы, которые можно выбрать префиксом в начале сообщения. + Для радиоканала нужен доступ через подходящее устройство, а для канала разума — соответствующая связь. + ([color={ $keyColor }];[/color]) отправляет в общий канал, ([color={ $keyColor }]:[/color]) выбирает радиоканал по букве, а ([color={ $keyColor }]+[/color]) выбирает канал коллективного разума. +guide-communication-channels-page-copy-hint = [color={ $keyColor }]Нажмите на ключ в таблице, чтобы скопировать его.[/color] -guide-communication-channels-search-placeholder = Фильтр каналов +guide-communication-channels-search-placeholder = Фильтр guide-communication-channels-header-key = Ключ guide-communication-channels-header-name = Канал guide-communication-channels-header-description = Описание @@ -17,29 +19,29 @@ guide-communication-channels-description-collective-mind-generic = Канал « guide-communication-channels-description-radio-common = Общий канал станции. Обычно доступен большинству гарнитур и отправляется через точку с запятой. guide-communication-channels-description-radio-centcom = Канал для связи с представителями Центрального Командования и специальными группами. guide-communication-channels-description-radio-command = Командный канал капитана, глав отделов и других должностей командования. -guide-communication-channels-description-radio-engineering = Канал инженерного отдела для ремонта, строительства, атмосферики и электроснабжения. +guide-communication-channels-description-radio-engineering = Канал инженерного отдела для ремонта, строительства, электроснабжения и атмосферики. guide-communication-channels-description-radio-medical = Канал медицинского отдела для врачей, химиков, парамедиков и обмена медицинской информацией. -guide-communication-channels-description-radio-science = Канал научного отдела для учёных, исследователей, робототехников, аномалистов и координации экспериментов. +guide-communication-channels-description-radio-science = Канал научного отдела для учёных, робототехников, аномалистов и координации экспериментов. guide-communication-channels-description-radio-security = Канал службы безопасности для патрулей, реагирования на вызовы и координации отдела. -guide-communication-channels-description-radio-service = Канал сервисного отдела: бар, кухня, ботаника, уборка, церковь, театр и прочие сервисные задачи. +guide-communication-channels-description-radio-service = Канал сервисного отдела: бар, кухня, ботаника, уборка, театр, церковь и прочие сервисные задачи. guide-communication-channels-description-radio-supply = Канал отдела снабжения для грузчиков, шахтёров, утилизаторов и заказов. -guide-communication-channels-description-radio-syndicate = Скрытый канал Синдиката. Доступен через соответствующие устройства и ключи. +guide-communication-channels-description-radio-syndicate = Скрытый канал Синдиката. Доступен через соответствующие устройства и ключи; шифрует передачу, скрывая имена говорящих. guide-communication-channels-description-radio-handheld = Локальный канал портативных раций. Для связи требуется портативная рация, настроенная на ту же частоту. guide-communication-channels-description-radio-freelance = Канал фрилансеров и независимых групп, если экипировка даёт к нему доступ. guide-communication-channels-description-radio-xenoborg = Канал ксеноборгов и связанных с ними систем. guide-communication-channels-description-radio-mothership = Канал ядра материнского шаттла для связи с ксеноборгами. Ксеноборги слышат его, но сами передают по каналу «Ксеноборги». guide-communication-channels-description-radio-legal = Юридический канал для магистрата, юристов и связанных правовых ролей. -guide-communication-channels-description-radio-future = Специальный дальний радиоканал будущего. +guide-communication-channels-description-radio-future = Специальный дальний радиоканал будущего. Будет доступен в будущем… guide-communication-channels-description-radio-radioshow = Канал радиовещания для ведущего и станционной радиостанции. guide-communication-channels-description-radio-cosmicradio = Канал космического культа и связанных с ним существ. -guide-communication-channels-description-collective-mind-dragonmind = Канал разума дракона для карпов, акул и связанных с ним существ. +guide-communication-channels-description-collective-mind-dragonmind = Канал коллективного разума дракона, карпов и акул. guide-communication-channels-description-collective-mind-lingmind = Канал коллективного разума генокрадов. guide-communication-channels-description-collective-mind-tidemind = Канал грейтайдов и связанных с ними событий. -guide-communication-channels-description-collective-mind-blobmind = Канал блоба для ядра, фабрик и других частей организма. +guide-communication-channels-description-collective-mind-blobmind = Канал блоба, объединяющий ядро, приспешников и другие части организма. guide-communication-channels-description-collective-mind-mansuslink = Связь Мансуса для еретиков и их призванных существ. -guide-communication-channels-description-collective-mind-abductormind = Канал серых и их команды. -guide-communication-channels-description-collective-mind-binary = Двоичный канал синтетиков и связанных устройств. +guide-communication-channels-description-collective-mind-abductormind = Канал связи серых и их команды. +guide-communication-channels-description-collective-mind-binary = Двоичный канал для синтетиков, киборгов, боргов, искусственного интеллекта и связанных устройств. guide-communication-channels-description-collective-mind-dronemind = Канал дронов. guide-communication-channels-description-collective-mind-mousemind = Канал мышей. guide-communication-channels-description-collective-mind-empathy = Эмпатическая связь существ, которым доступен этот разум.