Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Content.Server/_Lust/Borgs/BorgModuleInnateComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Server._Lust.Silicons.Borgs;

/// <summary>
/// Компонент, позволяющий давать боргам действия (экшены) и компоненты через модуль
/// </summary>
[RegisterComponent]
public sealed partial class BorgModuleInnateComponent : Component
{
/// <summary>
/// Предметы, которые активируются прямо в руке
/// </summary>
[DataField]
public List<EntProtoId?> UseItems = new();

/// <summary>
/// Предметы, с помощью которых можно взаимодействовать с сущностями
/// </summary>
[DataField]
public List<EntProtoId?> InteractionItems = new();

/// <summary>
/// Компоненты, которые будут добавлены боргу при установке модуля
/// Будут удалены после его изъятия!
/// </summary>
[DataField]
public ComponentRegistry InnateComponents = new();

/// <summary>
/// Айди добавленных предметов этим модулем
/// Данный список нужен сугубо для корректной очистки
/// </summary>
[ViewVariables, Access(typeof(BorgModuleInnateSystem))]
public List<EntityUid> InnateItems = new();

/// <summary>
/// Экшены для борга, созданные данным модулем
/// Данный список нужен сугубо для корректной очистки
/// </summary>
[ViewVariables, Access(typeof(BorgModuleInnateSystem))]
public List<EntityUid> Actions = new();
}
211 changes: 211 additions & 0 deletions Content.Server/_Lust/Borgs/BorgModuleInnateSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
using Content.Shared._Sunrise.Silicons.Borgs;
using Content.Shared.Actions;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.UserInterface;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Server._Lust.Silicons.Borgs;

/// <summary>
/// Система выдачи встраиваемых предметов и компонентов через модуль.
/// </summary>
public sealed class BorgModuleInnateSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedInteractionSystem _interactions = default!;

// Название контейнера-хранилища встроенных предметов
private const string InnateItemsContainerId = "module_innate_items";

// Прототипы действий над предметами
private static readonly EntProtoId InnateUseItemAction = "ModuleInnateUseItemAction";
private static readonly EntProtoId InnateInteractionItemAction = "ModuleInnateInteractionItemAction";

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<BorgModuleInnateComponent, BorgModuleInstalledEvent>(OnInstalled);
SubscribeLocalEvent<BorgModuleInnateComponent, BorgModuleUninstalledEvent>(OnUninstalled);

SubscribeLocalEvent<BorgModuleInnateComponent, ModuleInnateUseItem>(OnInnateUseItem);
SubscribeLocalEvent<BorgModuleInnateComponent, ModuleInnateInteractionItem>(OnInnateInteractionItem);
}

/// <summary>
/// Добавляет нужные компоненты и предметы при "установке" модуля
/// </summary>
private void OnInstalled(Entity<BorgModuleInnateComponent> module, ref BorgModuleInstalledEvent args)
{
var containerManager = EnsureComp<ContainerManagerComponent>(args.ChassisEnt);
_containers.EnsureContainer<Container>(args.ChassisEnt, InnateItemsContainerId, containerManager);

EntityManager.AddComponents(args.ChassisEnt, module.Comp.InnateComponents);

if (!_containers.TryGetContainer(args.ChassisEnt, InnateItemsContainerId, out var container))
return;

AddItems(args.ChassisEnt, module, container);
}

/// <summary>
/// Удаляет нужные компоненты и предметы при "удалении" модуля
/// </summary>
private void OnUninstalled(Entity<BorgModuleInnateComponent> module, ref BorgModuleUninstalledEvent args)
{
foreach (var action in module.Comp.Actions)
_actions.RemoveAction(args.ChassisEnt, action);
foreach (var item in module.Comp.InnateItems)
QueueDel(item);

module.Comp.Actions.Clear();
module.Comp.InnateItems.Clear();

EntityManager.RemoveComponents(args.ChassisEnt, module.Comp.InnateComponents);
}

/// <summary>
/// Добавляет предметы в контейнер, а также создаёт экшены их активации в модуле для тела киборга
/// </summary>
private void AddItems(EntityUid chassis, Entity<BorgModuleInnateComponent> module, BaseContainer container)
{
foreach (var itemProto in module.Comp.UseItems)
{
if (itemProto is null)
continue;

AddUseItem(itemProto.Value, chassis, module, container);
}

foreach (var itemProto in module.Comp.InteractionItems)
{
if (itemProto is null)
continue;

AddInteractionItem(itemProto.Value, chassis, module, container);
}
}

/// <summary>
/// Добавляет предмет, который активируется в руке, вместе с экшеном для его активации
/// </summary>
private void AddUseItem(
EntProtoId itemProto,
EntityUid chassis,
Entity<BorgModuleInnateComponent> module,
BaseContainer container
)
{
var item = CreateInnateItem(itemProto, module, container);
var ev = new ModuleInnateUseItem(item);
var action = CreateAction(item, ev, InnateUseItemAction);
AssignAction(chassis, module, action);
}

/// <summary>
/// Добавляет предмет, который активируется выбором цели, вместе с экшеном для его активации
/// </summary>
private void AddInteractionItem(
EntProtoId itemProto,
EntityUid chassis,
Entity<BorgModuleInnateComponent> module,
BaseContainer container
)
{
var item = CreateInnateItem(itemProto, module, container);
var ev = new ModuleInnateInteractionItem(item);
var action = CreateAction(item, ev, InnateInteractionItemAction);
AssignAction(chassis, module, action);
}

/// <summary>
/// Создает предмет для использования через экшены согласно прототипу в заданном контейнере
/// </summary>
/// <returns>Сущность предмета</returns>
private EntityUid CreateInnateItem(
EntProtoId itemProto,
Entity<BorgModuleInnateComponent> module,
BaseContainer container
)
{
var item = Spawn(itemProto);
module.Comp.InnateItems.Add(item);

// Модифицируем компач юай, чтобы борг наверняка мог его использовать
if (TryComp<ActivatableUIComponent>(item, out var activatableUIComponent))
{
activatableUIComponent.RequiresComplex = false;
activatableUIComponent.InHandsOnly = false;
activatableUIComponent.RequireActiveHand = false;
Dirty(item, activatableUIComponent);
}

// Сохраняем его в контейнере предметов модуля
_containers.Insert(item, container);

return item;
}

/// <summary>
/// Согласно прототипу и событию создает экшен для активации данной сущности-предмета
/// </summary>
/// <returns>Сущность экшена</returns>
private EntityUid CreateAction(EntityUid item, BaseActionEvent assignedEvent, EntProtoId actionProto)
{
var actionEnt = Spawn(actionProto);
// Подгружаем спрайт для экшена из прото предмета
_actions.SetIcon(actionEnt, new SpriteSpecifier.EntityPrototype(MetaData(item).EntityPrototype!.ID));
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Оператор ! подавляет предупреждение, но не защищает от NRE.

MetaData(item).EntityPrototype!.ID — если EntityPrototype окажется null (например, при горячей перезагрузке прототипа или некорректном YAML), будет выброшен NullReferenceException. Предпочтительнее явная проверка:

🛡️ Предлагаемое исправление
-        _actions.SetIcon(actionEnt, new SpriteSpecifier.EntityPrototype(MetaData(item).EntityPrototype!.ID));
+        var proto = MetaData(item).EntityPrototype;
+        if (proto != null)
+            _actions.SetIcon(actionEnt, new SpriteSpecifier.EntityPrototype(proto.ID));
📝 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.

Suggested change
_actions.SetIcon(actionEnt, new SpriteSpecifier.EntityPrototype(MetaData(item).EntityPrototype!.ID));
var proto = MetaData(item).EntityPrototype;
if (proto != null)
_actions.SetIcon(actionEnt, new SpriteSpecifier.EntityPrototype(proto.ID));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Content.Server/_Lust/Borgs/BorgModuleInnateSystem.cs` at line 159, The code
uses MetaData(item).EntityPrototype!.ID with a null-forgiving operator which can
still throw a NullReferenceException; change the call site in the
_actions.SetIcon flow to null-check MetaData(item).EntityPrototype before using
its ID (the symbols to edit are MetaData(item).EntityPrototype,
_actions.SetIcon, and SpriteSpecifier.EntityPrototype), and if EntityPrototype
is null, handle it gracefully (e.g., log a warning/error and skip setting the
sprite or use a safe fallback SpriteSpecifier) instead of dereferencing with !.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Тут надо думать - система предполагает, что все предметы созданы по прототипу как-никак. И в самой системе так и есть.

Copy link
Contributor

Choose a reason for hiding this comment

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

@crestoedge, понимаю вашу точку зрения. Действительно, в архитектуре системы все предметы создаются через Spawn(itemProto) с валидным прототипом, и при нормальной работе EntityPrototype всегда будет заполнен.

Использование ! здесь оправдано, если вы уверены в инвариантах системы. Единственный сценарий проблемы — это горячая перезагрузка прототипов или критическая ошибка в данных, но такие случаи обычно приводят к более глобальным проблемам.

Оставляйте как есть, если считаете, что это соответствует архитектурным гарантиям системы. 👍


🧠 Learnings used
Learnt from: Orvex07
Repo: space-sunrise/lust-station PR: 442
File: Resources/Prototypes/_Lust/CP/Weapons/hl2_weapons.yml:56-105
Timestamp: 2025-12-31T16:02:40.555Z
Learning: В проекте space-sunrise/lust-station при анализе PR с контентом в директории _Lust/CP необходимо проверять существование прототипов не только в файлах PR, но и в upstream директории _Sunrise, так как _Lust может использовать прототипы оружия, магазинов и других предметов из _Sunrise (например, MagazineMP7 определён в Resources/Prototypes/_Sunrise/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml).

Learnt from: Kendrick15
Repo: space-sunrise/lust-station PR: 301
File: Resources/Locale/ru-RU/_strings/_lust/flavors/flavor-profiles.ftl:1-1
Timestamp: 2025-07-08T21:33:23.476Z
Learning: Проект lust-station является взрослым контентом с ERP (Erotic Role Play) функциональностью, где активно используется ненормативная лексика в локализациях, включая акценты персонажей, комментарии разработчиков и игровой контент. Стиль локализации соответствует взрослой тематике проекта.

Learnt from: Esco-dev
Repo: space-sunrise/lust-station PR: 427
File: Resources/Prototypes/_Sunrise/Roles/Jobs/Command/nanotrasen_representative.yml:7-7
Timestamp: 2025-12-12T08:52:50.213Z
Learning: В проекте space-sunrise/lust-station используется комментарий "# Lust Edit" для маркировки изменений в файлах относительно upstream проекта sunrise-station. Эти комментарии являются частью системы документирования fork-специфичных изменений и не должны удаляться.

Learnt from: Esco-dev
Repo: space-sunrise/lust-station PR: 427
File: Resources/Prototypes/_Sunrise/Roles/Jobs/Command/nanotrasen_representative.yml:7-7
Timestamp: 2025-12-12T08:52:35.387Z
Learning: В проекте space-sunrise/lust-station используется комментарий "#Lust Edit" для маркировки изменений в файлах, специфичных для форка lust-station. Эти комментарии являются частью системы документирования изменений относительно upstream проекта sunrise-station и не должны удаляться.

Learnt from: darneya
Repo: space-sunrise/lust-station PR: 425
File: Resources/Prototypes/_Lust/CP/Clothing/jumpsuits.yml:197-208
Timestamp: 2025-12-27T09:48:52.949Z
Learning: В проекте space-sunrise/lust-station при добавлении контента Half-Life 2 (HL2) по техническому заданию пользователя Wein описания предметов должны строго следовать предоставленному ТЗ, даже если они могут показаться семантически несоответствующими названию предмета (например, ClothingUniformJumpsuitCPRefugee с описанием помощника на поле боя).

// Заготовка события для экшена
_actions.SetEvent(actionEnt, assignedEvent);

// Даем экшену название и описание предмета
_metadata.SetEntityName(actionEnt, MetaData(item).EntityName);
_metadata.SetEntityDescription(actionEnt, MetaData(item).EntityDescription);
return actionEnt;
}

/// <summary>
/// Добавляет экшен в шасси и сохраняет его в контейнере модуля
/// </summary>
private void AssignAction(EntityUid chassis, Entity<BorgModuleInnateComponent> module, EntityUid action)
{
// Добавляем экшн в список экшенов и в список компача
_actionContainer.AddAction(module.Owner, action);
_actions.AddAction(chassis, action, module.Owner);
module.Comp.Actions.Add(action);
}

/// <summary>
/// Обработчик события использования предмета как будто он в руке
/// </summary>
private void OnInnateUseItem(Entity<BorgModuleInnateComponent> ent, ref ModuleInnateUseItem args)
{
var ev = new UseInHandEvent(args.Performer);
RaiseLocalEvent(args.Item, ev);
args.Handled = true;
}

/// <summary>
/// Обработчик события использования предмета на заданной цели
/// </summary>
private void OnInnateInteractionItem(Entity<BorgModuleInnateComponent> ent, ref ModuleInnateInteractionItem args)
{
_interactions.InteractUsing(
args.Performer,
args.Item,
args.Target,
Transform(args.Target).Coordinates,
false,
false,
false
);
args.Handled = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ private void InitializeInteractions()

SubscribeLocalEvent<InteractionsComponent, GetVerbsEvent<AlternativeVerb>>(AddInteractionsVerb);
SubscribeLocalEvent<InteractionsComponent, ComponentInit>(OnInteractionsComponentInit);
SubscribeLocalEvent<InteractionsComponent, ComponentRemove>(OnInteractionsComponentRemove);

SubscribeLocalEvent<InteractionsComponent, ClothingDidEquippedEvent>(ClothingDidEquipped);
SubscribeLocalEvent<InteractionsComponent, ClothingDidUnequippedEvent>(ClothingDidUnequipped);
Expand Down Expand Up @@ -174,6 +175,11 @@ private void OnInteractionsComponentInit(EntityUid uid, InteractionsComponent co
_ui.SetUi(uid, InteractionWindowUiKey.Key, interfaceData);
}

private void OnInteractionsComponentRemove(Entity<InteractionsComponent> ent, ref ComponentRemove args)
{
_ui.CloseUi(ent.Owner, InteractionWindowUiKey.Key);
}

private void OnInteractionMessageReceived(Entity<InteractionsComponent> ent, ref InteractionMessage args)
{
var target = ent.Comp.CurrentTarget;
Expand Down Expand Up @@ -579,6 +585,9 @@ private void AddInteractionsVerb(Entity<InteractionsComponent> ent, ref GetVerbs
if (!TryComp<UserInterfaceComponent>(args.User, out var interfaceComponent))
return;

if (!HasComp<InteractionsComponent>(args.User))
return;

if (_mobState.IsIncapacitated(args.Target) || _mobState.IsIncapacitated(args.User))
return;

Expand Down
31 changes: 31 additions & 0 deletions Content.Shared/_Lust/Borgs/BorgModuleInnateEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Shared.Actions;

namespace Content.Shared._Sunrise.Silicons.Borgs;

/// <summary>
/// Ивент на активацию встроенного предмета
/// </summary>
public sealed partial class ModuleInnateUseItem : InstantActionEvent
{
public readonly EntityUid Item;

public ModuleInnateUseItem(EntityUid item)
: this()
{
Item = item;
}
}

/// <summary>
/// Ивент на активацию встроенного предмета с взаимодействием с целью
/// </summary>
public sealed partial class ModuleInnateInteractionItem : EntityTargetActionEvent
{
public readonly EntityUid Item;

public ModuleInnateInteractionItem(EntityUid item)
: this()
{
Item = item;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ent-BorgModuleLustful = lustful cyborg module
.desc = Questionable cyborg module that can be installed into any cyborg to fulfill lustful desires of the crew. Don't pay attention to white marks - that's thermal paste.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
research-technology-lustful-modules = Сyborg modules for sex
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ent-BorgModuleLustful = модуль похоти киборга
.desc = Сомнительный модуль, который может быть установлен в любого киборга для исполнения похотливых желаний экипажа. Не обращайте внимания на белые пятна - это термопаста.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
research-technology-lustful-modules = Модули киборгов для секса
1 change: 1 addition & 0 deletions Resources/Prototypes/Recipes/Lathes/Packs/robotics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
- BorgModuleAdvancedClowning
- BorgModuleAdvancedChemical
- BorgModuleAdvancedMining
- BorgModuleLustful # Lust-edit

- type: latheRecipePack
id: MechParts
Expand Down
Loading
Loading