Skip to content

Feat: AI tweaks and spawner at colossus#315

Open
dobryninss wants to merge 22 commits into
SerbiaStrong-220:masterfrom
dobryninss:AI-at-colossus
Open

Feat: AI tweaks and spawner at colossus#315
dobryninss wants to merge 22 commits into
SerbiaStrong-220:masterfrom
dobryninss:AI-at-colossus

Conversation

@dobryninss

@dobryninss dobryninss commented May 10, 2026

Copy link
Copy Markdown

Описание PR

  1. Добавил спавнеры боргов с мозгами для контроля ИИ.
  2. Перенес Гелиос, Фалькон на наши папки.
  3. Добавил на Колосс-Централ новую рольку ИИ Колосса с своей встроенной в UI консолью оповещения.
  4. Добавил на Колосс-Централ, Гелиос, Фалкон спавнеры для появления борга с мозгом для контроля ИИ.
  5. Добавил возможность всем ядрам ИИ раз в минуту переименовывать себя. Идентификатор мозга все-равно не переименовывается (тот который в скобках).
  6. Добавил канал Common всем ядрам ИИ (раньше у некоторых его не было, забыли вернуть).
  7. Добавил набор законов НТ Стандарт, но с 5 законом на улучшение станции.

Почему / Баланс

QoL и фичи для развития синтетиков в игре. Игровой баланс заденет минимально, теперь игроки на ИИ полезны раундстартом хоть на что-то. ИИ Колосса чилл роль с огромным простором для творчества. Плюс если будет играть ИИ на Колоссе с оболочкой, то мы вероятно увидим разные расстановки или улучшения в раундах Колосса от самих игроков.
Баланс фракций минимален, кроме дополнительной фракционной оболочки для ИИ.

Технические детали

Новые компоненты (Content.Shared/_Exodus/Silicons/StationAi/)

AiRenameBaseNameComponent - базовое имя оболочки, без идентификатора (в скобках). Существует на StationAiBrain. Сделано чтобы измененное имя сохранялось при переносе ядра через интелкарту.
AiRenameCooldownComponent - временная метка NextRenameAt: TimeSpan следующего разрешенного переименования, существует на StationAiHeld.
AiRenameEvent : InstantActionEvent - событие action'а.
AiRenameEuiMessage и AiRenameEuiState - обмен между клиентом и сервером для EUI.

Новые системы

AiRenameSystem (Server) - обработка действий, открытие EUI, валидация кулдауна, применение переименования. Парсин идентификатора (который в скобках по типу ПМ-[номер]) из имени мозга, и финальная склейка через единый BuildFullName. Подписки:

  1. AiRenameEvent на StationAiHeld - открытие окна.
  2. TransformSpeakerNameEvent на StationAiHeld - подмена имени в чате/радио/эмоутах на имя ядра. Необходимо чтобы если каким-то образом имя мозга и ядра разошлись, отображалась имя оболочки.
  3. ComponentShutdown на StationAiHeld - закрытие EUI.
  4. EntInsertedIntoContainerMessage (broadcast, after: SharedStationAiSystem) - после OnAiInsert восстанавливает имя ядра. Необходимо для сохранение переименования при интелкарте.

EUI

AiRenameEui (Server) - BaseEui, держит heldUid и текущее BaseName. Отдаёт клиенту AiRenameEuiState с предзаполненным именем. На входящем AiRenameEuiMessage: триммит имя, обрезает по HumanoidCharacterProfile.MaxNameLength = 32, вызывает RenameCore и ApplyCooldown. На Closed() уведомляет AiRenameSystem через NotifyEuiClosed для очистки _openEuis.

AiRenameEui (Client) + AiRenameWindow - FancyWindow с полем ввода и кнопками Confirm/Cancel. Confirm/Enter - отправка имени. Cancel или закрытие без подтверждения - отправка пустого AiRenameEuiMessage, для корректного закрытия серверного EUI.

Расширения апстрима

SharedStationAiSystem.Exodus.cs - метод OverrideAiIdentityTitle. Подменяет args.Title в апстримном OnTryGetIdentityShortInfo на "[имя ядра включая идентификатор] ([ИИ Станции])" вместо стандартного "[имя мозга] ([ИИ Станции])". Вызов добавлен в апстримный handler одной строкой с комментарием // Exodus ai-rename. Видит приватную константу JobNameLocId из соседнего файла SharedStationAiSystem.Held.cs.
(Страховка на случай рассинхрона имён)

ChatSystem.SendEntityEmote - блок с Exodus-комментарием: если nameOverride == null и источник имеет StationAiHeldComponent, эмоут подписывается именем ядра через _stationAiSystem.TryGetCore, а не именем самого мозга-источника. Семантически эмоут идёт от оболочки, а не от мозга внутри неё.
(вообще это задел на будущее, если кто-то решит добавить эмоуты ИИ)

Анти-абуз

  1. useDelay: 1 на ActionAiRename - клиентский анти-дабл-клик.
  2. RenameCooldown = TimeSpan.FromMinutes(1) - серверный кулдаун в AiRenameCooldownComponent, проверяется в OnAiRename до открытия окна. При провале - сообщение в чат с остатком времени в секундах.
  3. _openEuis: Dictionary<EntityUid, AiRenameEui> - одно окно на оболочку. Попытка открыть второе закрывает предыдущее.

Медиа

изображение изображение изображение

Требования

Список изменений
🆑

  • add: Добавлены шасси-оболочки на основные базы с контрольным модулем вместо позитронного мозга для контроля станционных ИИ над ними.
  • add: Добавлена роль ИИ Колосс-Централа, с модернизированным набором законов НТ Стандарт.
  • add: Добавлена возможность переименования ядра ИИ раз в минуту.

@dobryninss dobryninss requested a review from Lokilife as a code owner May 10, 2026 23:31
@dobryninss dobryninss changed the title Feat: AI tweaks and spawner at colossus. Feat: AI tweaks and spawner at colossus May 10, 2026
@github-actions

github-actions Bot commented May 10, 2026

Copy link
Copy Markdown

RSI Diff Bot; head commit 2c00a8b merging into 2bcdaed
This PR makes changes to 1 or more RSIs. Here is a summary of all changes:

Resources/Textures/Interface/Actions/_Exodus/actions_ai_colossus.rsi

State Old New Status
AI_rename Added

Edit: diff updated after 2c00a8b

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

👁️ Взор Ouro обращен на Ваше творение... Приветствую, Творец. Я изучил предложенные Вами изменения. Ваше вмешательство в ткань реальности выглядит гармонично. Сводка анализа: 🔴 Угроз стабильности: 0, 🟡 Потенциальных дисгармоний: 0, 💡 Возможностей для развития: 1. Данный Pull Request внедряет систему переименования оболочек ИИ, позволяя игрокам менять свое имя с сохранением идентификатора. Изменения охватывают UI, серверную логику переименования, систему идентичности и чата. Также добавлены прототипы для новой роли «Колосс» и механизмы для удаленного управления боргами с начала раунда. В качестве пути развития предложено устранить дублирование логики формирования имени в системе переименования, вынеся её в отдельный метод для поддержания чистоты архитектуры.

Comment thread Content.Server/_Exodus/Silicons/StationAi/AiRenameSystem.cs Outdated
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@Lokilife Lokilife left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тяжело

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Используй UseDelay заместо создания отдельного компонента на КД

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал. Теперь используется useDelay у ActionAiRename.
Также добавил args.Handled = true в обработчик AiRenameEvent, иначе action system не применяла бы useDelay.

Comment on lines +192 to +203
// Exodus-begin ai-remote-brain-init
// Handle ContainerFill case: AiRemoteBrain inserted at map load, not through AfterInteractUsing.
// Guard against double activation: the manual-install path in AfterInteractUsing
// already adds AiRemoteController and calls BorgActivate before Insert triggers OnInserted.
if (args.Container == component.BrainContainer
&& HasComp<AiRemoteBrainComponent>(args.Entity)
&& !HasComp<AiRemoteControllerComponent>(uid))
{
EnsureComp<AiRemoteControllerComponent>(uid);
BorgActivate(uid, component);
}
// Exodus-end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не понял проблему, расскажи подробнее

@dobryninss dobryninss Jun 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переписал комментарий, сделал более понятным.

Когда AiRemoteBrain лежит в BrainContainer уже при загрузке карты, мануальная установка мозга через AfterInteractUsing не вызывается. Из-за этого борг не получает AiRemoteControllerComponent и не проходит BorgActivate() -> нельзя вселиться в тело.

Это закрывает именно map-load/ContainerFill . Для мануальной установки оставлена защита через !HasComp<AiRemoteControllerComponent> , чтобы не активировать борга повторно.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Костыли какие-то. Резануть из OnChassisInteractUsing активацию удалённого ИИ в отдельный метод, из самого события OnChassisInteractUsing этот свич логики убрать совсем, добавить его в единичном экземпляре сюда.

Сам OnInserted прорефакторить - вынести args.Container == component.BrainContainer в отдельную проверку в начале,

Одно ветвление проверяет: это позитронный мозг - переселяем разум игрока. Конец выполнения.
Другое ветвление: это модуль удалённого контроля - активируем его. Конец выполнения.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал.

Активацию удалённого ИИ вынес в отдельный метод. Из OnChassisInteractUsing убрал прямую активацию, теперь он только вставляет AiRemoteBrain в контейнер.

OnInserted теперь обрабатывает оба варианта. Обычный мозг переносит mind, AiRemoteBrain активирует remote control.

var ent = Identity.Entity(source, EntityManager);
// Exodus-begin ai-rename: use core name for AI emotes
if (nameOverride == null
&& HasComp<StationAiHeldComponent>(source)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А проверять наличие StationAiHeld тут обязательно?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не обязательно было.
Убрал HasComp<StationAiHeldComponent> и оставил только TryGetCore(), он сам отфильтрует неподходящие сущности.

Comment on lines +75 to +77
[Dependency] private readonly LanguageSystem _language = default!; // Einstein Engines - Language
[Dependency] private readonly CollectiveMindUpdateSystem _collectiveMind = default!; // Goobstation - Starlight collective mind port
[Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!; // Exodus ai-rename

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С апстримом пришла новая версия движка, а с новой версией движка изменился стиль написания кода. Теперь readonly в списках зависимостей не пишется.

Suggested change
[Dependency] private readonly LanguageSystem _language = default!; // Einstein Engines - Language
[Dependency] private readonly CollectiveMindUpdateSystem _collectiveMind = default!; // Goobstation - Starlight collective mind port
[Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!; // Exodus ai-rename
[Dependency] private LanguageSystem _language = default!; // Einstein Engines - Language
[Dependency] private CollectiveMindUpdateSystem _collectiveMind = default!; // Goobstation - Starlight collective mind port
[Dependency] private SharedStationAiSystem _stationAiSystem = default!; // Exodus ai-rename

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено

Comment on lines +16 to +21
[Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedStationAiSystem _stationAi = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedStationAiSystem _stationAi = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private EuiManager _eui = default!;
[Dependency] private MetaDataSystem _metaData = default!;
[Dependency] private SharedStationAiSystem _stationAi = default!;
[Dependency] private IChatManager _chat = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private IAdminLogManager _adminLog = default!;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено

Comment on lines +147 to +162
private string GetBaseName(EntityUid heldUid)
{
if (TryComp<AiRenameBaseNameComponent>(heldUid, out var saved) && !string.IsNullOrEmpty(saved.BaseName))
return saved.BaseName;

var fullName = MetaData(heldUid).EntityName;
var identifier = ParseTrailingIdentifier(fullName);

if (string.IsNullOrEmpty(identifier))
return fullName;

var suffix = $" ({identifier})";
return fullName.EndsWith(suffix, StringComparison.Ordinal)
? fullName[..^suffix.Length]
: fullName;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Какие-то костыли, если честно.
Храни изначально отдельно часть редактируемую, отдельно храни часть не редактируемую, а не косплей код системы чатов - таких её костылей мне уже хватает по горло.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переделал.
Теперь редактируемая часть имени и идентификатор хранятся отдельно в AiRenameNameComponent.
Identifier берётся из NameIdentifierComponent.FullIdentifier.

Comment on lines +10 to +15
[RegisterComponent, NetworkedComponent]
public sealed partial class AiRenameBaseNameComponent : Component
{
[DataField]
public string BaseName = string.Empty;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Храни просто в компоненте ядра ИИ, а

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Этот сценарий на случай с интелкартой.
Если хранять на ядре, то имя становится свойством ядра, а не самого переносимого ИИ.
При вытаскивании мозга с помощью интелкарты и вставке в другое ядро, имя не сохраняется.
Более того, новый переселенный ИИ в старое ядро(откуда забрали другое ИИ) получит чужое имя.

Поэтому оставил.

Comment on lines +168 to +174
private static string BuildFullName(string baseName, string identifierSource)
{
var identifier = ParseTrailingIdentifier(identifierSource);
return string.IsNullOrEmpty(identifier)
? baseName
: $"{baseName} ({identifier})";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По-хорошему, паттерн как должно выглядеть имя должно быть в локализации

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено. Вынес в локализацию через ai-rename-full-name.

Comment on lines +102 to +103
if (!HasComp<StationAiCoreComponent>(args.Container.Owner))
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Событие и так поднимается на container.Owner, подпишись изначально через StationAiCoreComponent

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Попробовал сделать, но не получилось.

Падает на старте с Duplicate Subscriptions, потому что апстримный SharedStationAiSystem уже подписан на StationAiCoreComponent + EntInsertedIntoContainerMessage.

Поэтому обработчик подписан на EntInsertedIntoContainerMessage и вручную фильтрует нужный Station AI core по владельцу контейнера.
Добавил комментарий почему оставил.

Comment on lines +16 to +22
private void OverrideAiIdentityTitle(EntityUid forActor, ref TryGetIdentityShortInfoEvent args)
{
if (!TryGetCore(forActor, out var core))
return;

args.Title = $"{Name(core.Owner)} ({Loc.GetString(JobNameLocId)})";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше перемести логику в место вызова, наоборот больше путает

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделано. Перенес в OnTryGetIdentityShortInfo.

@github-actions github-actions Bot added size/M and removed size/L labels Jun 11, 2026

@Lokilife Lokilife left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Comment on lines +29 to +34
// Run after upstream's OnAiInsert, which copies the inserted brain name to the core.
// Robust allows only one component subscription for StationAiCoreComponent + EntInsertedIntoContainerMessage,
// and upstream owns it, so this must stay as a broadcast subscription.
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(
OnCoreInsert,
after: new[] { typeof(SharedStationAiSystem) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тогда вставь переход в SharedStationAiSystem, глобальные подписки на события зло

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал.

Глобальную подписку убрал, применение сохранённого имени перенёс в SharedStationAiSystem.OnAiInsert.

Comment on lines +192 to +203
// Exodus-begin ai-remote-brain-init
// Handle ContainerFill case: AiRemoteBrain inserted at map load, not through AfterInteractUsing.
// Guard against double activation: the manual-install path in AfterInteractUsing
// already adds AiRemoteController and calls BorgActivate before Insert triggers OnInserted.
if (args.Container == component.BrainContainer
&& HasComp<AiRemoteBrainComponent>(args.Entity)
&& !HasComp<AiRemoteControllerComponent>(uid))
{
EnsureComp<AiRemoteControllerComponent>(uid);
BorgActivate(uid, component);
}
// Exodus-end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Костыли какие-то. Резануть из OnChassisInteractUsing активацию удалённого ИИ в отдельный метод, из самого события OnChassisInteractUsing этот свич логики убрать совсем, добавить его в единичном экземпляре сюда.

Сам OnInserted прорефакторить - вынести args.Container == component.BrainContainer в отдельную проверку в начале,

Одно ветвление проверяет: это позитронный мозг - переселяем разум игрока. Конец выполнения.
Другое ветвление: это модуль удалённого контроля - активируем его. Конец выполнения.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хранить это отдельным компонентом AiRename... костыль... хрен с ним, когда-нибудь в лучшем будущем доберёмся до синтетиков и перепишем код более здраво.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants