-
Notifications
You must be signed in to change notification settings - Fork 154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PORT] NanoСhat #1055
[PORT] NanoСhat #1055
Conversation
WalkthroughВ этом обновлении добавлена интеграция NanoChat в различные подсистемы: изменена логика работы агентских ID-карт для отслеживания номера, расширен интерфейс NanoChat с новыми элементами ввода и отображения сообщений, а также внедрены новые серверные системы для обработки и передачи данных NanoChat. Изменения затрагивают как клиентскую, так и серверную части, а также обновления локализации, прототипов и метаданных визуальных компонентов. Changes
Sequence Diagram(s)sequenceDiagram
participant Пользователь
participant UI as NanoChatUiFragment
participant NanoChatUi
participant Server as NanoChatCartridgeSystem
participant Shared as SharedNanoChatSystem
Пользователь->>UI: Ввод сообщения
UI->>NanoChatUi: Генерация события OnMessageSent
NanoChatUi->>Server: Вызов SendNanoChatUiMessage(тип, номер, содержимое, должность)
Server->>Shared: Обработка и доставка сообщения
Shared-->>Server: Результат обработки
Server->>UI: Обновление состояния (UpdateState с новыми сообщениями)
UI->>Пользователь: Отображение обновлённого чата
sequenceDiagram
participant Пользователь
participant AID_UI as AgentIDCardWindow
participant AID_BUI as AgentIDCardBoundUserInterface
participant Server as AgentIDCardSystem
participant NanoChat as NanoChatCardComponent
Пользователь->>AID_UI: Изменение номера в LineEdit
AID_UI->>AID_BUI: Событие OnNumberChanged
AID_BUI->>Server: Отправка AgentIDCardNumberChangedMessage
Server->>NanoChat: Обновление NanoChat номера
Server->>AID_BUI: Отправка обновлённых данных (UpdateState)
AID_BUI->>AID_UI: Вызов SetCurrentNumber для обновления отображения
AID_UI->>Пользователь: Отображение обновлённого номера
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
🧹 Nitpick comments (45)
Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs (2)
22-27
: Рекомендуется улучшить документацию свойства.Текущая документация недостаточно подробно описывает назначение и использование свойства ScannedNanoChatData.
Предлагаю обновить документацию следующим образом:
- /// Corvax-Next-PDAChat: The last scanned NanoChat data, if any + /// <summary> + /// Содержит данные последнего отсканированного NanoChat сообщения. + /// Используется для хранения информации о получателях и содержании сообщения + /// после сканирования NanoChat карты. + /// Значение null означает отсутствие отсканированных данных. + /// </summary>
26-27
: Рекомендуется добавить ViewVariables атрибут.Для консистентности с другими свойствами класса и удобства отладки, следует добавить атрибут ViewVariables.
Предлагаю применить следующие изменения:
[DataField] + [ViewVariables] public NanoChatData? ScannedNanoChatData;
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml.cs (1)
10-16
: Добавьте проверку входных параметров и документацию.Рекомендуется:
- Добавить проверку на null для строковых параметров
- Добавить XML-документацию для класса и конструктора
+ /// <summary> + /// Представляет элемент лога в NanoChat. + /// </summary> public sealed partial class NanoChatLogEntry : BoxContainer { + /// <summary> + /// Инициализирует новый экземпляр элемента лога. + /// </summary> + /// <param name="number">Номер сообщения</param> + /// <param name="time">Время отправки</param> + /// <param name="message">Текст сообщения</param> + /// <exception cref="ArgumentNullException">Если time или message равны null</exception> public NanoChatLogEntry(int number, string time, string message) { + ArgumentNullException.ThrowIfNull(time); + ArgumentNullException.ThrowIfNull(message); + RobustXamlLoader.Load(this); NumberLabel.Text = number.ToString(); TimeLabel.Text = time; MessageLabel.Text = message; }Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml (2)
6-19
: Улучшите доступность и UX компонента.Текущая реализация может иметь проблемы с доступностью и юзабилити:
- При обрезании текста (ClipText="True") пользователь не видит полное содержимое
- Фиксированная ширина может создавать проблемы при локализации
<BoxContainer Orientation="Horizontal"> <Label Name="NumberLabel" Align="Right" SetWidth="26" - ClipText="True" /> + ClipText="True" + ToolTip="{Binding Text}" /> <Label Name="TimeLabel" Align="Center" SetWidth="100" - ClipText="True" /> + ClipText="True" + ToolTip="{Binding Text}" /> <Label Name="MessageLabel" Align="Left" MinWidth="390" HorizontalExpand="True" - ClipText="False" /> + ClipText="False" + ToolTip="{Binding Text}" />
1-21
: Рассмотрите использование стилей для унификации дизайна.Для улучшения поддержки и консистентности интерфейса рекомендуется вынести общие свойства в стили.
<BoxContainer xmlns="https://spacestation14.io" xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:styles="clr-namespace:Content.Client.Styles" Margin="4" Orientation="Vertical"> + <BoxContainer.Resources> + <Style TargetType="Label"> + <Setter Property="ClipText" Value="True" /> + <Setter Property="ToolTip" Value="{Binding Text}" /> + </Style> + </BoxContainer.Resources> <!-- ... остальной код ... -->Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs (2)
9-17
: Рекомендуется вынести цветовые константы в конфигурацию темы.Для улучшения поддержки и возможности изменения темы оформления, рекомендуется перенести цветовые константы в отдельный конфигурационный файл темы.
Предлагаемая структура:
// ThemeColors.cs public static class ThemeColors { public static class NanoChat { public static readonly Color OwnMessage = Color.FromHex("#173717d9"); public static readonly Color OtherMessage = Color.FromHex("#252525d9"); public static readonly Color Border = Color.FromHex("#40404066"); public static readonly Color Text = Color.FromHex("#dcdcdc"); public static readonly Color Error = Color.FromHex("#cc3333"); } }
46-47
: Удалите непрофессиональный комментарий!Комментарий "fuuuuuck" необходимо заменить на информативное описание проблемы или решения.
Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.NanoChat.cs (1)
16-36
: Оптимизация обработки обновления получателей.Текущая реализация создаёт новую копию словаря получателей при каждом обновлении. Для больших списков это может быть неэффективно.
Предлагаю оптимизировать код следующим образом:
- probe.ScannedNanoChatData = new NanoChatData( - new Dictionary<uint, NanoChatRecipient>(card.Recipients), - probe.ScannedNanoChatData.Value.Messages, - card.Number, - GetNetEntity(args.CardUid)); + var recipients = probe.ScannedNanoChatData.Value.Recipients; + recipients.Clear(); + foreach (var kvp in card.Recipients) + recipients[kvp.Key] = kvp.Value; + + probe.ScannedNanoChatData = new NanoChatData( + recipients, + probe.ScannedNanoChatData.Value.Messages, + card.Number, + GetNetEntity(args.CardUid));Content.Server/_CorvaxNext/NanoChat/NanoChatSystem.cs (3)
20-23
: Рекомендация использовать конфигурируемые вероятности вместо хардкода
В коде жёстко зашиты проценты вероятности (10%, 90%, 50%, 25%). Для более гибкой настройки и тестирования может быть полезно вынести эти значения в конфигурационные параметры или константы.
58-68
: Использование ScrambleMessages после стирания
Если стирание не произошло (90% случаев), вызываетсяScrambleMessages
. Логика выглядит целостно, но обратите внимание, что при некоторых сценариях может потребоваться уведомлять пользователя о смене получателей и содержимого, чтобы избежать путаницы.
73-102
: Оптимизация ScrambleMessages при больших объемах данных
В циклах вложенных сообщений потенциальна высокая сложность при большом количестве получателей и сообщений. Если NanoChat в будущем будет поддерживать множество записей, рассмотрите оптимизацию или отдельный поток обработки для избежания задержек в основном потоке.Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs (4)
19-35
: Метод UpdateState с различными состояниями
Реализация методаUpdateState
обрабатывает два сценария (NanoChatData и AccessLogs). Код выглядит читабельным. Рекомендуется дополнительно логгировать, почему именно выбран тот или иной путь (для отладки).
37-52
: Управление UI для NanoChat
МетодSetupNanoChatView
корректно изменяет заголовки и располагает текст. Возможно, стоит также выводить более детальную информацию о карте или описывать пустую информацию, еслиCardNumber
отсутствует.
61-119
: Метод DisplayNanoChatData и раздельный показ входящих/исходящих сообщений
Подход с двумя отдельными циклами для входящих и исходящих сообщений упрощает чтение, однако сводит все сообщения в один список. Можно рассмотреть отображение их вперемешку по временной метке, чтобы визуально соответствовать реальному диалогу.
122-124
: Метод DisplayAccessLogs
Вынос логики журнала доступа в отдельный метод повышает удобочитаемость. Текущий подход с реверсом списка даёт естественный порядок снизу вверх, что может сбивать с толку некоторых пользователей. Возможен вывод в хронологическом порядке сверху вниз.Content.Shared/_CorvaxNext/NanoChat/SharedNanoChatSystem.cs (3)
20-32
: Уточнить, какую информацию следует отображать при осмотре
Текущее поведение при отсутствии номера карты выглядит ожидаемо. Однако при необходимости в будущем можно дополнить логику — например, если потребуется отображать статус карты или дополнительную информацию. Убедитесь, что функциональность масштабируется под возможные новые требования.
220-241
: Уточнить семантику частичного удаления переписки
МетодTryDeleteChat
предлагает возможность удаления только чата из списка получателей, сохраняя сообщения (флагkeepMessages
). Если по бизнес-логике ожидается полная очистка истории, имеет смысл явно разделить сценарии «сохранить историю» и «полностью удалить».
248-270
: Упростить логику обеспечения существования получателя
ВEnsureRecipientExists
при отсутствии контакта метод добавляет новую запись. Желательно рассмотреть, нужно ли отдельное поведение при передачеnull
вrecipientInfo
, чтобы не смешивать ответственность создания и проверки. Это повысит читаемость и расширяемость кода.Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.cs (4)
74-105
: Проверить корректность фильтрации входящих сообщений
МетодOnMessage
отвергает сообщения, еслиmsg.RecipientNumber
илиmsg.Content
равныnull
. Возможно, стоит логировать ситуацию или уведомлять пользователя о некорректном вводе. Это поможет избежать вопросов, почему сообщение не доставлено.
214-259
: Учесть возможное отсутствие валидного номера у отправителя
ВHandleSendMessage
метод проверяетcard.Comp.Number
. Если оноnull
, отправка сообщения невозможна. Можно рассмотреть дополнительное уведомление пользователю или логику восстановления номера, чтобы избежать «тихого» игнорирования.
278-344
: Оптимизировать поиск получателей
При проверке получателей вAttemptMessageDelivery
система последовательно ищет все карточки, а затем в цикле проверяет все картриджи. Если число игроков и станций вырастет, это может стать узким местом. Возможна оптимизация, например, хеширование известных номеров или кеширование картриджей по номеру.
393-423
: Добавить гибкую настройку уведомлений
МетодHandleUnreadNotification
отправляет уведомление, если уведомления не отключены и статус непрочитанного ещё не установлен. Если впоследствии появятся категории уведомлений или приоритеты, может понадобиться более гибкая система фильтров.Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeComponent.cs (1)
9-26
: Рекомендуется добавить валидацию для RadioChannel!Хотя реализация в целом корректна, рекомендуется добавить проверку значения RadioChannel при инициализации, чтобы гарантировать существование указанного канала.
Предлагаю добавить валидацию в конструкторе или методе Initialize:
private void Initialize() { if (!_prototypeManager.HasIndex<RadioChannelPrototype>(RadioChannel)) { Logger.Warning($"Invalid radio channel {RadioChannel} specified for {nameof(NanoChatCartridgeComponent)}"); RadioChannel = "Common"; } }Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiState.cs (1)
15-29
: Рассмотрите использование инициализации с помощью init-only свойствТекущая реализация с конструктором работает корректно, но можно сделать код более лаконичным, используя init-only свойства C#.
- public readonly Dictionary<uint, NanoChatRecipient> Recipients = new(); - public readonly Dictionary<uint, List<NanoChatMessage>> Messages = new(); - public readonly uint? CurrentChat; - public readonly uint OwnNumber; - public readonly int MaxRecipients; - public readonly bool NotificationsMuted; + public Dictionary<uint, NanoChatRecipient> Recipients { get; init; } = new(); + public Dictionary<uint, List<NanoChatMessage>> Messages { get; init; } = new(); + public uint? CurrentChat { get; init; } + public uint OwnNumber { get; init; } + public int MaxRecipients { get; init; } + public bool NotificationsMuted { get; init; }Content.Shared/CartridgeLoader/Cartridges/LogProbeUiState.cs (1)
22-22
: Исправьте отступ для консистентностиВ строке используется табуляция вместо пробелов, что не соответствует стилю остального кода.
- NanoChatData = nanoChatData; // Corvax-Next-PDAChat + NanoChatData = nanoChatData; // Corvax-Next-PDAChatContent.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs (1)
20-38
: Отличная реализация обработки событий!Метод
SetRecipient
правильно управляет жизненным циклом обработчиков событий, очищая старые обработчики перед установкой новых. Визуальная обратная связь для выбранного состояния реализована корректно.Рассмотрите добавление проверки на null
Добавьте проверку параметра recipient для предотвращения потенциальных NullReferenceException.
public void SetRecipient(NanoChatRecipient recipient, uint number, bool isSelected) { + if (recipient == null) + throw new ArgumentNullException(nameof(recipient)); + // Remove old handler if it exists if (_pressHandler != null) ChatButton.OnPressed -= _pressHandler;Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUi.cs (1)
33-42
: Добавьте валидацию входных параметров.Метод
SendNanoChatUiMessage
не проверяет входные параметры на null или некорректные значения.Предлагаемое исправление:
private static void SendNanoChatUiMessage(NanoChatUiMessageType type, uint? number, string? content, string? job, BoundUserInterface userInterface) { + ArgumentNullException.ThrowIfNull(userInterface, nameof(userInterface)); + var nanoChatMessage = new NanoChatUiMessageEvent(type, number, content, job); var message = new CartridgeUiMessage(nanoChatMessage); userInterface.SendMessage(message); }Resources/Locale/ru-RU/_corvaxnext/cartridge-loader/nanochat.ftl (1)
12-12
: Улучшите формулировку сообщения о длине.Текущая формулировка может быть непонятна пользователю.
Предлагаемое исправление:
-nano-chat-message-too-long = Сообщение содержит ({$current}/{$max} символов) +nano-chat-message-too-long = Сообщение слишком длинное ({$current} из {$max} символов)Content.Shared/_CorvaxNext/NanoChat/NanoChatCardComponent.cs (2)
45-45
: Реализуйте ограничение частоты отправки сообщений.TODO комментарий указывает на необходимость реализации ограничения частоты отправки сообщений.
Хотите, чтобы я создал issue для отслеживания этой задачи и предложил реализацию системы ограничения частоты отправки сообщений?
39-39
: Вынесите максимальное количество получателей в конфигурацию.Жестко закодированное значение
MaxRecipients = 50
лучше вынести в конфигурационный файл для более гибкой настройки.Предлагаемое исправление:
[DataField] - public int MaxRecipients = 50; + public int MaxRecipients = IoCManager.Resolve<IConfigurationManager>() + .GetCVar(CCVars.NanoChatMaxRecipients);Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml (4)
21-25
: Рекомендуется добавить анимацию появления номера карты.Для улучшения UX, рассмотрите возможность добавления плавной анимации при изменении свойства
Visible
.<Label Name="CardNumberLabel" StyleClasses="LabelSubText" HorizontalAlignment="Center" Margin="0 0 0 8" - Visible="False"/> + Visible="False"> + <Label.Styles> + <Style Selector="Label"> + <Style.Animations> + <Animation Property="Opacity" + From="0.0" To="1.0" + Duration="0.3"/> + </Style.Animations> + </Style> + </Label.Styles> +</Label>
12-19
: Добавьте атрибут AccessText для улучшения доступности.Для заголовка следует добавить атрибут AccessText для улучшения доступности интерфейса.
<Label Name="TitleLabel" Text="{Loc 'log-probe-header-access'}" StyleClasses="LabelHeading" HorizontalAlignment="Center" + AccessText="True" Margin="0 0 0 8"/>
21-25
: Рассмотрите использование привязки данных для Visible.Вместо жесткого задания Visible="False", лучше использовать привязку данных для динамического управления видимостью.
<Label Name="CardNumberLabel" StyleClasses="LabelSubText" HorizontalAlignment="Center" Margin="0 0 0 8" - Visible="False"/> + Visible="{Binding HasCardNumber}"/>
28-32
: Предложение по улучшению макета колонок.Текущая реализация с фиксированной шириной колонок может вызвать проблемы при локализации на другие языки.
Рекомендуется использовать относительные размеры:
- <Label Align="Right" SetWidth="26" ClipText="True" Text="{Loc 'log-probe-label-number'}"/> - <Label Align="Center" SetWidth="100" ClipText="True" Text="{Loc 'log-probe-label-time'}"/> - <Label Name="ContentLabel" Align="Left" SetWidth="390" ClipText="True" Text="{Loc 'log-probe-label-accessor'}"/> + <Label Align="Right" HorizontalExpand="True" MinWidth="26" ClipText="True" Text="{Loc 'log-probe-label-number'}"/> + <Label Align="Center" HorizontalExpand="True" MinWidth="100" ClipText="True" Text="{Loc 'log-probe-label-time'}"/> + <Label Name="ContentLabel" Align="Left" HorizontalExpand="True" MinWidth="200" ClipText="True" Text="{Loc 'log-probe-label-accessor'}"/>Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatEntry.xaml (4)
5-9
: Добавьте атрибуты доступности для кнопки чата.Для улучшения доступности интерфейса, рекомендуется добавить роль и описание для кнопки.
<Button Name="ChatButton" StyleClasses="ButtonSquare" HorizontalExpand="True" MaxSize="137 64" - Margin="0 1"> + Margin="0 1" + ToolTip.Tip="{Loc 'nano-chat-open-conversation'}" + AccessKeyHint="O">
17-27
: Рассмотрите добавление анимации для индикатора непрочитанных сообщений.Плавная пульсация индикатора может улучшить привлечение внимания пользователя.
<PanelContainer Name="UnreadIndicator" MinSize="8 8" MaxSize="8 8" VerticalAlignment="Center" - Margin="0 0 6 0"> + Margin="0 0 6 0"> + <PanelContainer.Styles> + <Style Selector="PanelContainer"> + <Style.Animations> + <Animation Property="Opacity" + From="0.6" To="1.0" + Duration="1.0" + RepeatCount="INFINITY"/> + </Style.Animations> + </Style> + </PanelContainer.Styles>
5-9
: Добавьте ToolTip для улучшения UX.Кнопка чата должна иметь подсказку для улучшения пользовательского опыта.
<Button Name="ChatButton" StyleClasses="ButtonSquare" HorizontalExpand="True" MaxSize="137 64" + ToolTip="{Loc 'nano-chat-entry-tooltip'}" Margin="0 1">
17-27
: Рассмотрите вынесение стилей в отдельный ресурс.Стили индикатора непрочитанных сообщений лучше вынести в отдельный ресурс для переиспользования.
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NewChatPopup.xaml (2)
11-13
: Рекомендуется добавить валидацию на стороне UIДля улучшения UX рекомендуется добавить атрибуты валидации непосредственно в XAML:
- MaxLength для ограничения длины ввода
- ToolTip для отображения требований к вводу
- <LineEdit Name="NumberInput" - PlaceHolder="{Loc nano-chat-number-placeholder}" /> + <LineEdit Name="NumberInput" + MaxLength="4" + ToolTip="{Loc nano-chat-number-tooltip}" + PlaceHolder="{Loc nano-chat-number-placeholder}" />Also applies to: 21-23, 31-33
40-49
: Рекомендуется добавить горячие клавишиДля улучшения UX рекомендуется добавить горячие клавиши для кнопок:
- Enter для Create
- Escape для Cancel
<Button Name="CancelButton" Text="{Loc nano-chat-cancel}" StyleClasses="OpenRight" - MinSize="80 0" /> + MinSize="80 0" + Access.Key="Escape" /> <Button Name="CreateButton" Text="{Loc nano-chat-create}" StyleClasses="OpenLeft" MinSize="80 0" - Disabled="True" /> + Disabled="True" + Access.Key="Return" />Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NewChatPopup.xaml.cs (1)
52-57
: Добавить санитизацию для поля JobРекомендуется добавить очистку ввода от специальных символов и лишних пробелов для поля Job, как это сделано для других полей.
JobInput.OnTextChanged += args => { if (args.Text.Length > MaxInputLength) JobInput.Text = args.Text[..MaxInputLength]; + // Sanitize input + var newText = new string(JobInput.Text.Where(c => char.IsLetterOrDigit(c) || char.IsWhiteSpace(c)).ToArray()); + if (newText != JobInput.Text) + JobInput.Text = newText; };Content.Shared/Access/SharedAgentIDCardSystem.cs (1)
42-52
: Улучшить документацию нового класса сообщенийРекомендуется добавить полную документацию для нового класса
AgentIDCardNumberChangedMessage
.- // Corvax-Next-PDAChat - Add number change message + /// <summary> + /// Сообщение, отправляемое при изменении номера NanoChat в ID карте агента. + /// Используется для синхронизации состояния между клиентом и сервером. + /// </summary> [Serializable, NetSerializable] public sealed class AgentIDCardNumberChangedMessage : BoundUserInterfaceMessage { + /// <summary> + /// Новый номер NanoChat. + /// </summary> public uint Number { get; } public AgentIDCardNumberChangedMessage(uint number)Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs (1)
55-55
: Исправить отступОбнаружена проблема с форматированием кода - используется табуляция вместо пробелов.
- ent.Comp.ScannedNanoChatData = null; // Corvax-Next-PDAChat - Clear any previous NanoChat data + ent.Comp.ScannedNanoChatData = null; // Corvax-Next-PDAChat - Clear any previous NanoChat dataContent.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs (1)
59-59
: Удалите ненужный комментарий.Комментарий "putting this here because i can" не несет полезной информации и должен быть удален.
-// putting this here because i can
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml (1)
19-35
: Добавьте анимацию для улучшения отзывчивости интерфейса.Рекомендуется добавить анимацию появления сообщений для улучшения пользовательского опыта.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
Resources/Textures/_CorvaxNext/Interface/VerbIcons/bell.svg
is excluded by!**/*.svg
Resources/Textures/_CorvaxNext/Interface/VerbIcons/bell.svg.png
is excluded by!**/*.png
Resources/Textures/_CorvaxNext/Interface/VerbIcons/bell_muted.png
is excluded by!**/*.png
Resources/Textures/_CorvaxNext/Misc/program_icons.rsi/nanochat.png
is excluded by!**/*.png
Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/cart-chat.png
is excluded by!**/*.png
📒 Files selected for processing (46)
Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
(2 hunks)Content.Client/Access/UI/AgentIDCardWindow.xaml
(1 hunks)Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
(2 hunks)Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
(1 hunks)Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
(1 hunks)Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
(2 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatEntry.xaml
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml.cs
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUi.cs
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NewChatPopup.xaml
(1 hunks)Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NewChatPopup.xaml.cs
(1 hunks)Content.Client/_CorvaxNext/NanoChat/NanoChatSystem.cs
(1 hunks)Content.Server/Access/Systems/AgentIDCardSystem.cs
(5 hunks)Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs
(2 hunks)Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs
(5 hunks)Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.NanoChat.cs
(1 hunks)Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeComponent.cs
(1 hunks)Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.cs
(1 hunks)Content.Server/_CorvaxNext/NanoChat/NanoChatSystem.cs
(1 hunks)Content.Shared/Access/SharedAgentIDCardSystem.cs
(1 hunks)Content.Shared/CartridgeLoader/Cartridges/LogProbeUiState.cs
(2 hunks)Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs
(1 hunks)Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiState.cs
(1 hunks)Content.Shared/_CorvaxNext/NanoChat/NanoChatCardComponent.cs
(1 hunks)Content.Shared/_CorvaxNext/NanoChat/SharedNanoChatSystem.cs
(1 hunks)Resources/Locale/en-US/_corvaxnext/access/components/agent-id-card-component.ftl
(1 hunks)Resources/Locale/en-US/_corvaxnext/cartridge-loader/nanochat.ftl
(1 hunks)Resources/Locale/en-US/_corvaxnext/nanochat/components/nanochat-card-component.ftl
(1 hunks)Resources/Locale/ru-RU/_corvaxnext/access/components/agent-id-card-component.ftl
(1 hunks)Resources/Locale/ru-RU/_corvaxnext/cartridge-loader/nanochat.ftl
(1 hunks)Resources/Locale/ru-RU/_corvaxnext/nanochat/components/nanochat-card-component.ftl
(1 hunks)Resources/Locale/ru-RU/backmen/disease/disease.ftl
(1 hunks)Resources/Locale/ru-RU/backmen/disease/swab.ftl
(1 hunks)Resources/Prototypes/Entities/Objects/Devices/pda.yml
(6 hunks)Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
(2 hunks)Resources/Prototypes/_CorvaxNext/Entities/Objects/Devices/cartridges.yml
(1 hunks)Resources/Prototypes/_CorvaxNext/name_identifier_groups.yml
(1 hunks)Resources/Textures/_CorvaxNext/Interface/VerbIcons/ATTRIBUTION.txt
(1 hunks)Resources/Textures/_CorvaxNext/Misc/program_icons.rsi/meta.json
(1 hunks)Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/meta.json
(1 hunks)
✅ Files skipped from review due to trivial changes (7)
- Resources/Locale/en-US/_corvaxnext/access/components/agent-id-card-component.ftl
- Resources/Textures/_CorvaxNext/Interface/VerbIcons/ATTRIBUTION.txt
- Resources/Locale/ru-RU/backmen/disease/disease.ftl
- Resources/Textures/_CorvaxNext/Objects/Devices/cartridge.rsi/meta.json
- Resources/Textures/_CorvaxNext/Misc/program_icons.rsi/meta.json
- Resources/Locale/ru-RU/_corvaxnext/access/components/agent-id-card-component.ftl
- Resources/Locale/ru-RU/backmen/disease/swab.ftl
⏰ Context from checks skipped due to timeout of 300000ms (3)
- GitHub Check: Test Packaging
- GitHub Check: build (ubuntu-latest)
- GitHub Check: build (ubuntu-latest)
🔇 Additional comments (53)
Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs (1)
2-2
: Добавление директивы using для NanoChat функционала.Директива необходима для использования типа NanoChatData из пространства имен Corvax.
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs (1)
1-8
: Корректные импорты и пространство имён!Структура импортов и пространства имён соответствует архитектуре проекта.
Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.NanoChat.cs (1)
8-14
: Структура класса и инициализация реализованы корректно!Хорошее разделение логики с использованием partial класса и правильная подписка на события.
Content.Server/_CorvaxNext/NanoChat/NanoChatSystem.cs (2)
33-46
: Проверить корректность удаления сообщений при одновременной микроволновке
МетодOnMicrowaved
мгновенно очищает или изменяет коллекциюMessages
, однако при возможном одновременном доступе других систем могут возникать гонки. Убедитесь, что любое параллельное использование исключено или корректно синхронизировано.
104-118
: Реализация Fisher-Yates для перестановки
Алгоритм перемешивания символов выглядит корректным и завершённым. При этом будьте осторожны с многострочными сообщениями и любыми спецсимволами — в некоторых случаях можно добавить проверку на юникод-суррогаты или эмодзи, хотя в большинстве случаев это не критично.Content.Server/Access/Systems/AgentIDCardSystem.cs (5)
12-13
: Подключение пространства имён NanoChat
Импорт пакетаContent.Shared._CorvaxNext.NanoChat
выглядит уместно. Убедитесь, что не появляется конфликтов с другими системами, использующими похожие пространства имён.
22-22
: Добавление SharedNanoChatSystem в зависимости
Добавление_nanoChat
обеспечивает взаимодействие с NanoChat-картами. Убедитесь в корректной инициализации, чтобы исключить ситуации, когда_nanoChat
вызывается до своегоInitialize()
.
33-34
: Подписка на AgentIDCardNumberChangedMessage
Подключение нового события для изменения номера карты NanoChat. Общая структура соответствует остальным сообщениям системы.
36-44
: Обработка OnNumberChanged
Метод корректно ищет компонентNanoChatCardComponent
и затем устанавливает новый номер через_nanoChat
. Рекомендуется проверить, не будет ли нарушено логическое состояние, если карта уже имеет другой номер, и как это обрабатывается с точки зрения пользовательского интерфейса.
112-120
: Обновлённая логика AfterUIOpen
Передача текущего номера NanoChat в состояние интерфейса упрощает отображение данных. Убедитесь, что логика обновления номера (черезOnNumberChanged
) и интерфейса остаётся синхронизированной для пользователя.Content.Shared/_CorvaxNext/NanoChat/SharedNanoChatSystem.cs (2)
39-57
: Убедиться в корректной обработке отсутствующего компонента
МетодыGetNumber
иSetNumber
используютResolve
для поискаNanoChatCardComponent
. Это удобный подход, однако в случаях, когдаcard
может быть неверно сопоставлен или отсутствовать, стоит проверить, не возникнут ли случайные ошибки в вызовах из других систем.
118-132
: Учесть возможную параллельную модификацию словаря
При добавлении нового сообщения одновременно несколькими источниками может возникнуть гонка, если выполнение не строго однопоточное. Если в контексте игры все системные вызовы происходят последовательно, риск минимален. Но при расширении или тестировании многопоточности стоит рассмотреть блокировку словаря при записи, чтобы предотвратить потенциальные коллизии.Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeSystem.cs (1)
132-152
: Обратить внимание на выбор названия для контакта
При создании нового чата (HandleNewChat
) имя (msg.Content
) используется для обозначения получателя. Убедитесь, что бизнес-логика предусматривает хранение имени в этом поле и что его изменение действительно сигнализирует о названиях получателей (и не конфликтует с текстом сообщения).Resources/Locale/en-US/_corvaxnext/nanochat/components/nanochat-card-component.ftl (1)
1-5
: Локализация реализована корректно!Строки локализации хорошо структурированы и содержат всю необходимую информацию для пользователя. Переменные правильно используются в шаблонах.
Resources/Locale/ru-RU/_corvaxnext/nanochat/components/nanochat-card-component.ftl (1)
1-5
: Перевод выполнен качественно!Русские строки локализации точно передают смысл оригинала, при этом звучат естественно. Форматирование и использование переменных соответствует стандартам.
Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatCartridgeComponent.cs (1)
6-8
: Компонент правильно зарегистрирован!Атрибуты регистрации и доступа корректно настроены для компонента.
Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs (1)
26-26
: Изменение в передаче состояния реализовано корректно!Передача полного объекта состояния вместо отдельного свойства позволяет более гибко обрабатывать данные в UI-фрагменте. Изменение хорошо задокументировано комментарием.
Content.Client/Access/UI/AgentIDCardWindow.xaml (1)
9-12
: Корректное добавление поля для номера NanoChat!Поле для ввода номера NanoChat хорошо интегрировано в существующий интерфейс. Плейсхолдер "#0000" наглядно показывает ожидаемый формат.
Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiState.cs (1)
5-13
: Хорошая структура класса состояния UI!Класс правильно помечен как сериализуемый и использует неизменяемые поля для хранения данных состояния. Структура данных логично организована для управления чатами и получателями.
Content.Shared/CartridgeLoader/Cartridges/LogProbeUiState.cs (1)
14-17
: Хорошее документирование нового функционала!Добавление поддержки NanoChat хорошо задокументировано с помощью XML-комментариев.
Resources/Locale/en-US/_corvaxnext/cartridge-loader/nanochat.ftl (1)
1-37
: Локализация выполнена корректно!Файл содержит все необходимые строки для интерфейса NanoChat с правильным форматированием и поддержкой динамического контента.
Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml (6)
12-19
: Хорошая структура заголовка!Добавление заголовка с использованием
LabelHeading
и центрирование улучшает визуальную иерархию интерфейса.
28-32
: Проверьте доступность интерфейса.Убедитесь, что текст в колонках имеет достаточный контраст и размер для комфортного чтения. Также рекомендуется добавить всплывающие подсказки для обрезанного текста.
<BoxContainer Orientation="Horizontal"> <Label Align="Right" SetWidth="26" ClipText="True" Text="{Loc 'log-probe-label-number'}" + ToolTip.Tip="{Binding Text, RelativeSource={RelativeSource Self}}"/> <Label Align="Center" SetWidth="100" ClipText="True" Text="{Loc 'log-probe-label-time'}" + ToolTip.Tip="{Binding Text, RelativeSource={RelativeSource Self}}"/> <Label Name="ContentLabel" Align="Left" SetWidth="390" ClipText="True" Text="{Loc 'log-probe-label-accessor'}" + ToolTip.Tip="{Binding Text, RelativeSource={RelativeSource Self}}"/> </BoxContainer>
28-32
: LGTM! Хорошая структура заголовков колонок.Структура заголовков колонок хорошо организована с использованием фиксированной ширины и выравнивания.
4-5
: Правильная реорганизация контейнеров!Вертикальная ориентация корневого контейнера и вложенного BoxContainer обеспечивает логичное расположение элементов интерфейса сверху вниз.
Also applies to: 12-12
14-18
: Хорошее добавление заголовка!Заголовок правильно локализован и отцентрирован. Использование StyleClasses="LabelHeading" обеспечивает единый стиль с другими заголовками.
21-25
:❓ Verification inconclusive
Проверьте логику видимости CardNumberLabel.
Label изначально скрыт (Visible="False"). Убедитесь, что в code-behind правильно реализована логика отображения номера карты.
🏁 Script executed:
#!/bin/bash # Поиск логики управления видимостью CardNumberLabel ast-grep --pattern 'CardNumberLabel.Visible = $_'Length of output: 51
Проверьте логику управления видимостью CardNumberLabel
В XAML элемент CardNumberLabel изначально скрыт (Visible="False"). Однако поиск через
ast-grep --pattern 'CardNumberLabel.Visible = $_'
не обнаружил прямых изменений свойства в code-behind, что вызывает вопросы:
- Если управление видимостью осуществляется программно, убедитесь, что соответствующий код действительно изменяет состояние элемента (например, присваивая
CardNumberLabel.Visible = true
в нужных случаях).- Если применяется привязка или иной косвенный метод (например, изменение через data-binding или команды), убедитесь, что этот механизм корректно работает и обновляет UI согласно ожидаемой логике.
Пожалуйста, проведите дополнительную проверку работы логики изменения видимости либо вручную, либо с помощью дополнительного поиска в коде.
Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs (1)
22-22
:❌ Incorrect review comment
Отсутствует реализация метода InitializeNanoChat
В файле вызывается метод
InitializeNanoChat()
, но его реализация отсутствует. Необходимо добавить реализацию или предоставить информацию о том, где она находится.
🏁 Script executed:
#!/bin/bash # Search for InitializeNanoChat implementation rg -l "InitializeNanoChat"Length of output: 200
Реализация метода обнаружена
При поиске реализации методаInitializeNanoChat
были найдены следующие файлы:
Content.Server/_CorvaxNext/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.NanoChat.cs
NanoChat.patch
Таким образом, реализация метода присутствует в кодовой базе, а не отсутствует. Рекомендуется обновить комментарий, указав, что метод реализован в отдельном файле (например, добавить комментарий с указанием файла реализации) или пояснить архитектурное решение.
Likely an incorrect or invalid review comment.
Content.Client/Access/UI/AgentIDCardWindow.xaml.cs (3)
24-24
: Константа и событие корректно определены!Константа
MaxNumberLength
и событиеOnNumberChanged
правильно определены для обработки номера NanoChat.Also applies to: 29-29
42-63
: Обработка ввода номера реализована корректно!Код включает:
- Валидацию длины номера
- Фильтрацию на ввод только цифр
- Обработку граничных случаев
73-76
: Метод форматирования номера реализован правильно!Метод
SetCurrentNumber
корректно обрабатывает:
- Форматирование номера с ведущими нулями
- Случай с null значением
Content.Shared/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiMessageEvent.cs (6)
6-46
: Класс события сообщения реализован корректно!
- Правильно настроена сериализация
- Все свойства хорошо документированы
- Конструктор имеет опциональные параметры
49-57
: Перечисление типов сообщений определено корректно!Enum
NanoChatUiMessageType
охватывает все необходимые действия для работы чата.
61-97
: Структура получателя реализована корректно!
- Правильно настроена сериализация
- Все свойства хорошо документированы
- Конструктор имеет все необходимые параметры
100-137
: Структура сообщения реализована корректно!
- Правильно настроена сериализация
- Все свойства хорошо документированы
- Реализовано отслеживание статуса доставки
144-154
: Структура данных чата реализована корректно!
- Использован современный синтаксис C# record
- Правильно настроена сериализация
- Все свойства имеют правильные типы данных
160-160
: События обновления получателя и получения сообщения реализованы корректно!
- Использован атрибут [ByRefEvent]
- Использован современный синтаксис C# record
- События хорошо документированы
Also applies to: 166-166
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml (3)
16-75
: Заголовок интерфейса реализован корректно!
- Включены все необходимые элементы управления
- Правильно структурирована разметка
- Корректно определены иконки и подсказки
83-103
: Список чатов реализован корректно!
- Реализована прокрутка
- Обработано состояние пустого списка
- Правильно определены ограничения макета
108-164
: Область сообщений реализована корректно!
- Включены все необходимые компоненты
- Правильно реализована область ввода
- Корректно обрабатываются счетчик символов и кнопка отправки
Content.Client/_CorvaxNext/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs (4)
17-80
: Инициализация и настройка событий реализованы корректно!
- Правильно определены константы и зависимости
- Хорошо организованы обработчики событий
- Корректно реализована валидация ввода
82-114
: Обработка сообщений реализована корректно!
- Реализовано предсказание сообщений
- Обработаны ошибочные ситуации
- Правильно обновляется интерфейс
116-144
: Выбор и удаление чатов реализованы корректно!
- Правильно реализована логика выбора
- Предотвращен повторный выбор
- Корректно обрабатывается удаление
145-253
: Методы обновления интерфейса реализованы корректно!
- Обновления охватывают все аспекты
- Правильно управляется состояние
- Корректно обрабатывается синхронизация интерфейса
Resources/Prototypes/_CorvaxNext/name_identifier_groups.yml (1)
1-5
: Проверка определения группы идентификаторов NanoChat.
Добавлен новый блок с типомnameIdentifierGroup
для идентификаторов NanoChat с параметромmaxValue: 9999
. Всё оформлено корректно и соответствует ожидаемой конфигурации.Resources/Prototypes/_CorvaxNext/Entities/Objects/Devices/cartridges.yml (1)
1-21
: Добавление сущности NanoChatCartridge.
Новая сущностьNanoChatCartridge
создана с необходимыми компонентами:
• КомпонентSprite
для визуального отображения (используется спрайтcartridge.rsi
с состояниемcart-chat
).
• КомпонентUIFragment
для привязки UI-фрагмента, отвечающего за обработку интерфейса NanoChat.
• Собственный компонентNanoChatCartridge
для специфичного функционала системы NanoChat.
• КомпонентCartridge
, задающий программное имя (nano-chat-program-name
) и иконку (спрайт изprogram_icons.rsi
с состояниемnanochat
).
• КомпонентActiveRadio
для определения каналов связи (каналCommon
).
Структура выглядит логично и единообразно – изменений не требуется.Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml (1)
846-848
: Интеграция компонента NanoChatCard в ID-карты.
Новый компонентNanoChatCard
добавлен в состав идентификационной карточки с параметромnotificationsMuted: true
, что отключает уведомления для NanoChat режима. Убедитесь, что данное изменение согласовано с логикой обновлённого UI и обработкой данных в бэкенде.Resources/Prototypes/Entities/Objects/Devices/pda.yml (6)
144-146
: Добавление NanoChatCartridge в BaseSecurityPDA.
В сущностиBaseSecurityPDA
в список предустановленных картриджей добавленNanoChatCartridge
с комментарием# Corvax-Next-PDAChat
. Это изменение корректно расширяет функционал для обеспечения NanoChat в системе безопасности.
856-858
: Добавление NanoChatCartridge в HoSPDA.
В блокеHoSPDA
добавлена строка сNanoChatCartridge
в список предустановленных картриджей. Рекомендуется проверить корректность интеграции с остальными компонентами PDA, чтобы функционал NanoChat работал без конфликтов.
927-929
: Внедрение NanoChatCartridge в CentcomPDA.
В сущностиCentcomPDA
дополнительно к другим картриджам добавленNanoChatCartridge
, что позволяет использовать NanoChat на этом устройстве. Проверка на соответствие общей архитектуре устройства выполнена успешно.
1090-1102
: Добавление NanoChatCartridge в ERTLeaderPDA.
Новый предустановленный картриджNanoChatCartridge
добавлен в конфигурациюERTLeaderPDA
. Необходимо убедиться, что пользовательский интерфейс для этого устройства корректно обрабатывает функции NanoChat.
1260-1271
: Интеграция NanoChatCartridge в DetectivePDA.
ВDetectivePDA
внесена модификация с добавлениемNanoChatCartridge
в список картриджей. Данное изменение расширяет функциональность обмена сообщениями, и структура блока выглядит корректно – проверьте отсутствие конфликтов с другими компонентами.
1297-1299
: Добавление NanoChatCartridge в BrigmedicPDA.
В сущностиBrigmedicPDA
добавленNanoChatCartridge
для активации NanoChat-функционала. Изменение интегрируется в общий список предустановленных картриджей и соответствует целям расширения коммуникационных возможностей устройств.
public NanoChatMessageBubble() | ||
{ | ||
RobustXamlLoader.Load(this); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Добавьте обработку ошибок при загрузке XAML!
Отсутствие обработки ошибок при загрузке XAML может привести к сбоям во время выполнения. Рекомендуется добавить try-catch блок.
Предлагаемое исправление:
public NanoChatMessageBubble()
{
- RobustXamlLoader.Load(this);
+ try
+ {
+ RobustXamlLoader.Load(this);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"Ошибка при загрузке XAML для NanoChatMessageBubble: {ex}");
+ throw;
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public NanoChatMessageBubble() | |
{ | |
RobustXamlLoader.Load(this); | |
} | |
public NanoChatMessageBubble() | |
{ | |
try | |
{ | |
RobustXamlLoader.Load(this); | |
} | |
catch (Exception ex) | |
{ | |
Logger.Error($"Ошибка при загрузке XAML для NanoChatMessageBubble: {ex}"); | |
throw; | |
} | |
} |
public void SetMessage(NanoChatMessage message, bool isOwnMessage) | ||
{ | ||
if (MessagePanel.PanelOverride is not StyleBoxFlat) | ||
return; | ||
|
||
// Configure message appearance | ||
var style = (StyleBoxFlat)MessagePanel.PanelOverride; | ||
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor; | ||
style.BorderColor = BorderColor; | ||
|
||
// Set message content | ||
MessageText.Text = message.Content; | ||
MessageText.Modulate = TextColor; | ||
|
||
// Show delivery failed text if needed (only for own messages) | ||
DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed; | ||
if (DeliveryFailedLabel.Visible) | ||
DeliveryFailedLabel.Modulate = ErrorColor; | ||
|
||
// For own messages: FlexSpace -> MessagePanel -> RightSpacer | ||
// For other messages: LeftSpacer -> MessagePanel -> FlexSpace | ||
MessageContainer.RemoveAllChildren(); | ||
|
||
// fuuuuuck | ||
MessageBox.Parent?.RemoveChild(MessageBox); | ||
|
||
if (isOwnMessage) | ||
{ | ||
MessageContainer.AddChild(FlexSpace); | ||
MessageContainer.AddChild(MessageBox); | ||
MessageContainer.AddChild(RightSpacer); | ||
} | ||
else | ||
{ | ||
MessageContainer.AddChild(LeftSpacer); | ||
MessageContainer.AddChild(MessageBox); | ||
MessageContainer.AddChild(FlexSpace); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Добавьте проверки на null и упростите манипуляции с UI!
В методе SetMessage есть несколько моментов, требующих улучшения:
- Отсутствуют проверки на null для UI элементов
- Сложная логика манипуляции с дочерними элементами может быть упрощена
Предлагаемое исправление:
public void SetMessage(NanoChatMessage message, bool isOwnMessage)
{
+ if (MessagePanel?.PanelOverride is not StyleBoxFlat style)
+ return;
+
+ if (MessageText == null || MessageContainer == null || MessageBox == null)
+ return;
- if (MessagePanel.PanelOverride is not StyleBoxFlat)
- return;
-
// Configure message appearance
- var style = (StyleBoxFlat)MessagePanel.PanelOverride;
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor;
style.BorderColor = BorderColor;
// Set message content
MessageText.Text = message.Content;
MessageText.Modulate = TextColor;
// Show delivery failed text if needed (only for own messages)
- DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed;
- if (DeliveryFailedLabel.Visible)
+ if (DeliveryFailedLabel != null)
+ {
+ DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed;
+ if (DeliveryFailedLabel.Visible)
DeliveryFailedLabel.Modulate = ErrorColor;
+ }
MessageContainer.RemoveAllChildren();
- MessageBox.Parent?.RemoveChild(MessageBox);
+
+ var elements = isOwnMessage
+ ? new Control[] { FlexSpace, MessageBox, RightSpacer }
+ : new Control[] { LeftSpacer, MessageBox, FlexSpace };
- if (isOwnMessage)
- {
- MessageContainer.AddChild(FlexSpace);
- MessageContainer.AddChild(MessageBox);
- MessageContainer.AddChild(RightSpacer);
- }
- else
- {
- MessageContainer.AddChild(LeftSpacer);
- MessageContainer.AddChild(MessageBox);
- MessageContainer.AddChild(FlexSpace);
- }
+ foreach (var element in elements)
+ {
+ if (element != null)
+ MessageContainer.AddChild(element);
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public void SetMessage(NanoChatMessage message, bool isOwnMessage) | |
{ | |
if (MessagePanel.PanelOverride is not StyleBoxFlat) | |
return; | |
// Configure message appearance | |
var style = (StyleBoxFlat)MessagePanel.PanelOverride; | |
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor; | |
style.BorderColor = BorderColor; | |
// Set message content | |
MessageText.Text = message.Content; | |
MessageText.Modulate = TextColor; | |
// Show delivery failed text if needed (only for own messages) | |
DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed; | |
if (DeliveryFailedLabel.Visible) | |
DeliveryFailedLabel.Modulate = ErrorColor; | |
// For own messages: FlexSpace -> MessagePanel -> RightSpacer | |
// For other messages: LeftSpacer -> MessagePanel -> FlexSpace | |
MessageContainer.RemoveAllChildren(); | |
// fuuuuuck | |
MessageBox.Parent?.RemoveChild(MessageBox); | |
if (isOwnMessage) | |
{ | |
MessageContainer.AddChild(FlexSpace); | |
MessageContainer.AddChild(MessageBox); | |
MessageContainer.AddChild(RightSpacer); | |
} | |
else | |
{ | |
MessageContainer.AddChild(LeftSpacer); | |
MessageContainer.AddChild(MessageBox); | |
MessageContainer.AddChild(FlexSpace); | |
} | |
} | |
public void SetMessage(NanoChatMessage message, bool isOwnMessage) | |
{ | |
if (MessagePanel?.PanelOverride is not StyleBoxFlat style) | |
return; | |
if (MessageText == null || MessageContainer == null || MessageBox == null) | |
return; | |
// Configure message appearance | |
style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor; | |
style.BorderColor = BorderColor; | |
// Set message content | |
MessageText.Text = message.Content; | |
MessageText.Modulate = TextColor; | |
// Show delivery failed text if needed (only for own messages) | |
if (DeliveryFailedLabel != null) | |
{ | |
DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed; | |
if (DeliveryFailedLabel.Visible) | |
DeliveryFailedLabel.Modulate = ErrorColor; | |
} | |
MessageContainer.RemoveAllChildren(); | |
var elements = isOwnMessage | |
? new Control[] { FlexSpace, MessageBox, RightSpacer } | |
: new Control[] { LeftSpacer, MessageBox, FlexSpace }; | |
foreach (var element in elements) | |
{ | |
if (element != null) | |
MessageContainer.AddChild(element); | |
} | |
} |
private void ScanNanoChatCard(Entity<LogProbeCartridgeComponent> ent, | ||
CartridgeAfterInteractEvent args, | ||
EntityUid target, | ||
NanoChatCardComponent card) | ||
{ | ||
_audioSystem.PlayEntity(ent.Comp.SoundScan, | ||
args.InteractEvent.User, | ||
target, | ||
AudioHelpers.WithVariation(0.25f, _random)); | ||
_popupSystem.PopupCursor(Loc.GetString("log-probe-scan-nanochat", ("card", target)), args.InteractEvent.User); | ||
|
||
ent.Comp.PulledAccessLogs.Clear(); | ||
|
||
ent.Comp.ScannedNanoChatData = new NanoChatData( | ||
new Dictionary<uint, NanoChatRecipient>(card.Recipients), | ||
new Dictionary<uint, List<NanoChatMessage>>(card.Messages), | ||
card.Number, | ||
GetNetEntity(target) | ||
); | ||
|
||
UpdateUiState(ent, args.Loader); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Проверка безопасности сканирования карты.
Метод корректно очищает старые данные и обновляет состояние, но стоит проверить наличие ограничений на размер сообщений и списка получателей.
Давайте проверим, есть ли в системе ограничения на размер данных:
🏁 Script executed:
#!/bin/bash
# Поиск ограничений на размер данных в компонентах NanoChat
# Проверяем ограничения в компоненте NanoChatCardComponent
rg -A 5 "NanoChatCardComponent"
# Ищем валидацию размера сообщений
rg -A 5 "Messages.*Add|Recipients.*Add"
Length of output: 63876
ВНИМАНИЕ: проверка безопасности сканирования NanoChat карты
Метод ScanNanoChatCard
корректно сбрасывает предыдущие данные, обновляет состояние интерфейса и обеспечивает обратную связь (звуковые сигналы и всплывающие уведомления). Однако наблюдается отсутствие явных ограничений на количество сообщений и получателей, что может привести к потенциальным проблемам с производительностью или утечке памяти при чрезмерно больших объёмах данных.
Рекомендуемые действия:
- Проверьте, предусматривается ли в компонентах (например, в
NanoChatCardComponent
и системе NanoChat) валидация или ограничение на максимальное количество элементов в словаряхRecipients
иMessages
. - При отсутствии таких проверок, рассмотрите возможность введения ограничений (например, максимальное число сообщений или получателей) и соответствующей обработки ошибок или логирования при превышении этих лимитов.
private void OnMessageReceived(ref NanoChatMessageReceivedEvent args) | ||
{ | ||
var query = EntityQueryEnumerator<LogProbeCartridgeComponent, CartridgeComponent>(); | ||
while (query.MoveNext(out var uid, out var probe, out var cartridge)) | ||
{ | ||
if (probe.ScannedNanoChatData == null || GetEntity(probe.ScannedNanoChatData.Value.Card) != args.CardUid) | ||
continue; | ||
|
||
if (!TryComp<NanoChatCardComponent>(args.CardUid, out var card)) | ||
continue; | ||
|
||
probe.ScannedNanoChatData = new NanoChatData( | ||
probe.ScannedNanoChatData.Value.Recipients, | ||
new Dictionary<uint, List<NanoChatMessage>>(card.Messages), | ||
card.Number, | ||
GetNetEntity(args.CardUid)); | ||
|
||
if (cartridge.LoaderUid != null) | ||
UpdateUiState((uid, probe), cartridge.LoaderUid.Value); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Устранение дублирования кода в обработчиках событий.
Обработчики OnRecipientUpdated
и OnMessageReceived
содержат много повторяющейся логики запросов и валидации.
Предлагаю вынести общую логику в отдельный метод:
+ private bool TryGetProbeAndCard(EntityUid cardUid,
+ out (EntityUid Uid, LogProbeCartridgeComponent Probe, CartridgeComponent Cartridge) result,
+ out NanoChatCardComponent card)
+ {
+ card = default!;
+ result = default;
+
+ var query = EntityQueryEnumerator<LogProbeCartridgeComponent, CartridgeComponent>();
+ while (query.MoveNext(out var uid, out var probe, out var cartridge))
+ {
+ if (probe.ScannedNanoChatData == null ||
+ GetEntity(probe.ScannedNanoChatData.Value.Card) != cardUid)
+ continue;
+
+ if (!TryComp<NanoChatCardComponent>(cardUid, out card))
+ return false;
+
+ result = (uid, probe, cartridge);
+ return true;
+ }
+ return false;
+ }
Затем использовать его в обработчиках:
- var query = EntityQueryEnumerator<LogProbeCartridgeComponent, CartridgeComponent>();
- while (query.MoveNext(out var uid, out var probe, out var cartridge))
- {
- if (probe.ScannedNanoChatData == null || GetEntity(probe.ScannedNanoChatData.Value.Card) != args.CardUid)
- continue;
-
- if (!TryComp<NanoChatCardComponent>(args.CardUid, out var card))
- continue;
+ if (!TryGetProbeAndCard(args.CardUid, out var result, out var card))
+ return;
+
+ var (uid, probe, cartridge) = result;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private void OnMessageReceived(ref NanoChatMessageReceivedEvent args) | |
{ | |
var query = EntityQueryEnumerator<LogProbeCartridgeComponent, CartridgeComponent>(); | |
while (query.MoveNext(out var uid, out var probe, out var cartridge)) | |
{ | |
if (probe.ScannedNanoChatData == null || GetEntity(probe.ScannedNanoChatData.Value.Card) != args.CardUid) | |
continue; | |
if (!TryComp<NanoChatCardComponent>(args.CardUid, out var card)) | |
continue; | |
probe.ScannedNanoChatData = new NanoChatData( | |
probe.ScannedNanoChatData.Value.Recipients, | |
new Dictionary<uint, List<NanoChatMessage>>(card.Messages), | |
card.Number, | |
GetNetEntity(args.CardUid)); | |
if (cartridge.LoaderUid != null) | |
UpdateUiState((uid, probe), cartridge.LoaderUid.Value); | |
} | |
} | |
private bool TryGetProbeAndCard(EntityUid cardUid, | |
out (EntityUid Uid, LogProbeCartridgeComponent Probe, CartridgeComponent Cartridge) result, | |
out NanoChatCardComponent card) | |
{ | |
card = default!; | |
result = default; | |
var query = EntityQueryEnumerator<LogProbeCartridgeComponent, CartridgeComponent>(); | |
while (query.MoveNext(out var uid, out var probe, out var cartridge)) | |
{ | |
if (probe.ScannedNanoChatData == null || | |
GetEntity(probe.ScannedNanoChatData.Value.Card) != cardUid) | |
continue; | |
if (!TryComp<NanoChatCardComponent>(cardUid, out card)) | |
return false; | |
result = (uid, probe, cartridge); | |
return true; | |
} | |
return false; | |
} | |
private void OnMessageReceived(ref NanoChatMessageReceivedEvent args) | |
{ | |
if (!TryGetProbeAndCard(args.CardUid, out var result, out var card)) | |
return; | |
var (uid, probe, cartridge) = result; | |
probe.ScannedNanoChatData = new NanoChatData( | |
probe.ScannedNanoChatData.Value.Recipients, | |
new Dictionary<uint, List<NanoChatMessage>>(card.Messages), | |
card.Number, | |
GetNetEntity(args.CardUid)); | |
if (cartridge.LoaderUid != null) | |
UpdateUiState((uid, probe), cartridge.LoaderUid.Value); | |
} |
// Corvax-Next-PDAChat-Start - Copy NanoChat data if available | ||
if (TryComp<NanoChatCardComponent>(args.Target, out var targetNanoChat) && | ||
TryComp<NanoChatCardComponent>(uid, out var agentNanoChat)) | ||
{ | ||
// First clear existing data | ||
_nanoChat.Clear((uid, agentNanoChat)); | ||
|
||
// Copy the number | ||
if (_nanoChat.GetNumber((args.Target.Value, targetNanoChat)) is { } number) | ||
_nanoChat.SetNumber((uid, agentNanoChat), number); | ||
|
||
// Copy all recipients and their messages | ||
foreach (var (recipientNumber, recipient) in _nanoChat.GetRecipients((args.Target.Value, targetNanoChat))) | ||
{ | ||
_nanoChat.SetRecipient((uid, agentNanoChat), recipientNumber, recipient); | ||
|
||
if (_nanoChat.GetMessagesForRecipient((args.Target.Value, targetNanoChat), recipientNumber) is not | ||
{ } messages) | ||
continue; | ||
|
||
foreach (var message in messages) | ||
{ | ||
_nanoChat.AddMessage((uid, agentNanoChat), recipientNumber, message); | ||
} | ||
} | ||
} | ||
// Corvax-Next-PDAChat-End | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Копирование NanoChat-данных
Реализована логика копирования чатов с одной карты на другую при взаимодействии. Потенциально необходимо убедиться, что при копировании большего объёма диалогов всё корректно обрабатывается и не возникает переполнений или задержек. Можно добавить логирование результата копирования.
private const int MaxInputLength = 16; | ||
private const int MaxNumberLength = 4; // i hardcoded it to be 4 so suffer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Константы требуют рефакторинга
Рекомендуется:
- Перенести константы в конфигурационный файл
- Убрать комментарий о "страдании"
- Добавить документацию о причине выбора значений
NumberInput.OnTextChanged += _ => ValidateInputs(); | ||
NameInput.OnTextChanged += _ => ValidateInputs(); | ||
|
||
// Input validation | ||
NumberInput.OnTextChanged += args => | ||
{ | ||
if (args.Text.Length > MaxNumberLength) | ||
NumberInput.Text = args.Text[..MaxNumberLength]; | ||
|
||
// Filter to digits only | ||
var newText = string.Concat(NumberInput.Text.Where(char.IsDigit)); | ||
if (newText != NumberInput.Text) | ||
NumberInput.Text = newText; | ||
|
||
ValidateInputs(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Исправить дублирование обработчиков событий
У NumberInput.OnTextChanged
определено два обработчика, что может привести к непредсказуемому поведению.
- NumberInput.OnTextChanged += _ => ValidateInputs();
NumberInput.OnTextChanged += args =>
{
if (args.Text.Length > MaxNumberLength)
NumberInput.Text = args.Text[..MaxNumberLength];
// Filter to digits only
var newText = string.Concat(NumberInput.Text.Where(char.IsDigit));
if (newText != NumberInput.Text)
NumberInput.Text = newText;
ValidateInputs();
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
NumberInput.OnTextChanged += _ => ValidateInputs(); | |
NameInput.OnTextChanged += _ => ValidateInputs(); | |
// Input validation | |
NumberInput.OnTextChanged += args => | |
{ | |
if (args.Text.Length > MaxNumberLength) | |
NumberInput.Text = args.Text[..MaxNumberLength]; | |
// Filter to digits only | |
var newText = string.Concat(NumberInput.Text.Where(char.IsDigit)); | |
if (newText != NumberInput.Text) | |
NumberInput.Text = newText; | |
ValidateInputs(); | |
}; | |
NameInput.OnTextChanged += _ => ValidateInputs(); | |
// Input validation | |
NumberInput.OnTextChanged += args => | |
{ | |
if (args.Text.Length > MaxNumberLength) | |
NumberInput.Text = args.Text[..MaxNumberLength]; | |
// Filter to digits only | |
var newText = string.Concat(NumberInput.Text.Where(char.IsDigit)); | |
if (newText != NumberInput.Text) | |
NumberInput.Text = newText; | |
ValidateInputs(); | |
}; |
public uint? CurrentNumber { get; } // Corvax-Next-PDAChat | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Добавить XML документацию
Необходимо добавить XML документацию для нового свойства CurrentNumber
с описанием его назначения и возможных значений.
+ /// <summary>
+ /// Текущий номер NanoChat, связанный с ID картой.
+ /// Null означает, что номер не установлен.
+ /// </summary>
public uint? CurrentNumber { get; } // Corvax-Next-PDAChat
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public uint? CurrentNumber { get; } // Corvax-Next-PDAChat | |
/// <summary> | |
/// Текущий номер NanoChat, связанный с ID картой. | |
/// Null означает, что номер не установлен. | |
/// </summary> | |
public uint? CurrentNumber { get; } // Corvax-Next-PDAChat |
if (TryComp<NanoChatCardComponent>(target, out var nanoChatCard)) | ||
{ | ||
ScanNanoChatCard(ent, args, target, nanoChatCard); | ||
args.InteractEvent.Handled = true; | ||
return; | ||
} | ||
// Corvax-Next-PDAChat-End |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Добавить обработку ошибок
Рекомендуется добавить обработку ошибок при сканировании NanoChat карты и информативные сообщения для пользователя в случае неудачи.
if (TryComp<NanoChatCardComponent>(target, out var nanoChatCard))
{
+ try
+ {
ScanNanoChatCard(ent, args, target, nanoChatCard);
+ }
+ catch (Exception ex)
+ {
+ _popupSystem.PopupCursor(Loc.GetString("log-probe-scan-failed"), args.InteractEvent.User);
+ Log.Error($"Failed to scan NanoChat card: {ex}");
+ }
args.InteractEvent.Handled = true;
return;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (TryComp<NanoChatCardComponent>(target, out var nanoChatCard)) | |
{ | |
ScanNanoChatCard(ent, args, target, nanoChatCard); | |
args.InteractEvent.Handled = true; | |
return; | |
} | |
// Corvax-Next-PDAChat-End | |
if (TryComp<NanoChatCardComponent>(target, out var nanoChatCard)) | |
{ | |
try | |
{ | |
ScanNanoChatCard(ent, args, target, nanoChatCard); | |
} | |
catch (Exception ex) | |
{ | |
_popupSystem.PopupCursor(Loc.GetString("log-probe-scan-failed"), args.InteractEvent.User); | |
Log.Error($"Failed to scan NanoChat card: {ex}"); | |
} | |
args.InteractEvent.Handled = true; | |
return; | |
} | |
// Corvax-Next-PDAChat-End |
<RichTextLabel Name="NameLabel" | ||
StyleClasses="LabelHeading" | ||
HorizontalExpand="True" | ||
HorizontalAlignment="Center" | ||
VerticalAlignment="Center" | ||
Margin="0 -2 0 0" /> | ||
<Label Name="JobLabel" | ||
StyleClasses="LabelSubText" | ||
HorizontalExpand="True" | ||
ClipText="False" | ||
HorizontalAlignment="Center" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Добавьте обработку переполнения текста.
Для меток имени и должности следует добавить обработку случаев переполнения текста.
<RichTextLabel Name="NameLabel"
StyleClasses="LabelHeading"
HorizontalExpand="True"
HorizontalAlignment="Center"
VerticalAlignment="Center"
+ ClipText="True"
Margin="0 -2 0 0" />
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<RichTextLabel Name="NameLabel" | |
StyleClasses="LabelHeading" | |
HorizontalExpand="True" | |
HorizontalAlignment="Center" | |
VerticalAlignment="Center" | |
Margin="0 -2 0 0" /> | |
<Label Name="JobLabel" | |
StyleClasses="LabelSubText" | |
HorizontalExpand="True" | |
ClipText="False" | |
HorizontalAlignment="Center" /> | |
<RichTextLabel Name="NameLabel" | |
StyleClasses="LabelHeading" | |
HorizontalExpand="True" | |
HorizontalAlignment="Center" | |
VerticalAlignment="Center" | |
ClipText="True" | |
Margin="0 -2 0 0" /> | |
<Label Name="JobLabel" | |
StyleClasses="LabelSubText" | |
HorizontalExpand="True" | |
ClipText="False" | |
HorizontalAlignment="Center" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Он поклялся своей мамой что этот ПР работает
Описание PR
Порт NanoChat, ведь Тресту было лень фиксить свой PR.
Оригинальный PR - space-syndicate/space-station-14-next#153
Медиа
Soon...
Тип PR
Изменения
🆑 CatBG
Summary by CodeRabbit
Новые возможности
Локализация