Skip to content

Bodytypes(test version) в доработке на долго - #567

Draft
Fooksy2304 wants to merge 1 commit into
makura-games:masterfrom
Fooksy2304:Bodytypes
Draft

Fooksy2304 wants to merge 1 commit into
makura-games:masterfrom
Fooksy2304:Bodytypes

Conversation

@Fooksy2304

@Fooksy2304 Fooksy2304 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Краткое описание

Ссылка на багрепорт/Предложение

Медиа (Видео/Скриншоты)

Changelog

🆑 ВАШЕ_ИМЯ

  • add: ТЕКСТ
  • remove: ТЕКСТ
  • tweak: ТЕКСТ
  • fix: ТЕКСТ

Summary by CodeRabbit

  • Новые возможности
    • Добавлена независимая настройка формы тела, размера груди и ягодиц.
    • В редакторе профиля появились отдельные ползунки и локализованные подписи.
    • Настройки тела сохраняются в профиле и применяются при загрузке персонажа.
    • Добавлены новые варианты телосложения и визуальные слои для персонажей.
    • Улучшено отображение облегающей одежды с учетом выбранных параметров тела.
    • Добавлена поддержка новых карт деформации для корректного рендеринга одежды.

@github-actions

Copy link
Copy Markdown
Contributor

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

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi

State Old New Status
head_f Added
l_arm Added
l_foot Added
l_hand Added
l_leg_f Added
r_arm Added
r_foot Added
r_hand Added
r_leg_f Added
torso_f Added
torso_f_big Added
torso_f_fit Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi

State Old New Status
head_m Added
l_arm Added
l_foot Added
l_hand Added
l_leg Added
r_arm Added
r_foot Added
r_hand Added
r_leg Added
torso_m Added
torso_m_big Added
torso_m_fit Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi

State Old New Status
size_0 Added
size_1 Added
size_2 Added
size_3 Added
size_4 Added
size_5 Added
size_6 Added
size_7 Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi

State Old New Status
breast_a Added
breast_aa Added
breast_b Added
breast_c Added
breast_d Added
breast_dd Added
breast_e Added
breast_f Added
butt_athletic Added
butt_large Added
butt_standard Added
shape_apple_f Added
shape_apple_m Added
shape_hourglass_f Added
shape_hourglass_m Added
shape_pear_f Added
shape_pear_m Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi

State Old New Status
head_f Added
l_arm Added
l_foot Added
l_hand Added
l_leg_f Added
r_arm Added
r_foot Added
r_hand Added
r_leg_f Added
torso_f Added
torso_f_big Added
torso_f_fit Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi

State Old New Status
head_m Added
l_arm Added
l_foot Added
l_hand Added
l_leg Added
r_arm Added
r_foot Added
r_hand Added
r_leg Added
torso_m Added
torso_m_big Added
torso_m_fit Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi

State Old New Status
head_f Added
l_arm Added
l_foot Added
l_hand Added
l_leg_f Added
r_arm Added
r_foot Added
r_hand Added
r_leg_f Added
torso_f Added
torso_f_big Added
torso_f_fit Added

Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi

State Old New Status
head_m Added
l_arm Added
l_foot Added
l_hand Added
l_leg Added
r_arm Added
r_foot Added
r_hand Added
r_leg Added
torso_m Added
torso_m_big Added
torso_m_fit Added

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Добавлена независимая кастомизация формы тела, груди и ягодиц: параметры профиля сохраняются в БД, редактируются тремя слайдерами и применяются к appearance. Добавлены новые прототипы, спрайты и шейдер для комбинированных displacement-карт в одежде.

Changes

Кастомизация тела и сохранение

Layer / File(s) Summary
Поля профиля, валидация и БД
Content.Shared/Humanoid/*, Content.Shared/Preferences/*, Content.Server/Database/*, Content.Server.Database/Migrations/*
Добавлены размеры груди и ягодиц, ограничения по полу, сериализация профиля и миграции PostgreSQL/SQLite с колонками breast_size и butt_size.
Прототипы и ресурсы тела
Content.Shared/_Sunrise/BodyType.cs, Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs, Resources/Prototypes/_Lust/*, Resources/Textures/_Lust/*
Добавлены формы тела, размеры, спрайтовые слои, displacement-карты и метаданные RSI.
Редактор профиля
Content.Client/Lobby/UI/HumanoidProfileEditor.*, Resources/Locale/*/body-customization.ftl
Единый выбор типа тела заменён на слайдеры формы, груди и ягодиц с локализованными подписями и обновлением предпросмотра.
Комбинированный displacement-рендеринг
Content.Client/DisplacementMap/DisplacementMapSystem.cs, Content.Client/Clothing/ClientClothingSystem.cs, Resources/Textures/_Lust/Shaders/*
Добавлены многослойный шейдер, выбор допустимых карт по слоту одежды и добавление shape/breast/butt displacement-слоёв в оборудование.

Estimated code review effort: 5 (Critical) | ~90 minutes

Suggested labels: 📚 Изменения: Локализация 📚, 🖌️ Спрайты 🖌️, 📦 YAML | Прототип 📦, 🪟 Интерфейс 🪟

Suggested reviewers: kanopus952

Sequence Diagram(s)

sequenceDiagram
  participant ProfileEditor
  participant HumanoidAppearanceSystem
  participant ClientClothingSystem
  participant DisplacementMapSystem
  ProfileEditor->>HumanoidAppearanceSystem: устанавливает BodyType, BreastSize и ButtSize
  HumanoidAppearanceSystem->>ClientClothingSystem: передаёт appearance и слои тела
  ClientClothingSystem->>DisplacementMapSystem: передаёт разрешённые displacement-карты
  DisplacementMapSystem->>ClientClothingSystem: добавляет комбинированные слои шейдера
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Ss14 C# Rules ❌ Error В vanilla C# внесены новые поля/методы (HumanoidCharacterProfile, HumanoidAppearanceComponent, SharedHumanoidAppearanceSystem) вместо fork partials, что нарушает ss14-upstream-maintenance. Перенесите fork-логики в partial-классы под _Sunrise/_Lust, а в vanilla оставьте лишь минимальные хуки с корректными edit-маркерами.
Docstring Coverage ⚠️ Warning Docstring coverage is 52.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Заголовок слишком общий и не отражает конкретное изменение в PR. Укажите основной результат изменения, например добавление body customization и связанных прототипов/миграций.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Ss14 Bridge Sync ✅ Passed В диффе нет изменений в .agents/rules или .agents/skills, и bridge-файлы в .agent/.claude/.cursor/.github не затронуты.
Ss14 Fork/Project Folder Selection ✅ Passed Активный форк Lust: remote lust-station, новые fork-owned файлы/ассеты в _Lust, Fire/_Scp маркеров нет; _Sunrise тронут лишь хуками Lust.
Ss14 Yaml/Ftl Rules ✅ Passed В изменённых YAML/FTL нарушений не нашёл: IDs CamelCase, FTL-ключи kebab-case, entity-ключи корректны, англ. fallback не сломан.
Ss14 Prototype ↔ Ftl Parity ✅ Passed Изменены только species/bodyType и абстрактные entity-основы; player-facing body-shape-ключи в FTL добавлены, ent-* для новых entity не требуется.
Ss14 Prediction Safety ✅ Passed В shared-коде нет новых недетерминированных предикт-эффектов: только сетевые поля, детерминированные lookup'и и копирование профиля.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.Client/Clothing/ClientClothingSystem.cs`:
- Around line 436-461: Pass the already computed displacementData as the first
argument to TryAddBodyCustomizationDisplacements instead of null, while
preserving the existing shapeDisplacement, breastDisplacement, and
buttDisplacement arguments and fallback behavior. This ensures the base clothing
displacement is combined with body customization in the clothing layering flow.

In `@Content.Client/DisplacementMap/DisplacementMapSystem.cs`:
- Around line 158-190: Set each use{Prefix}Displacement shader flag only after
successfully resolving the corresponding map in the displacement-layer loop.
Ensure missing layerSize and 32x32 fallback maps leave the flag disabled while
preserving the existing error and continue behavior, and remove or avoid the
earlier unconditional flag assignment in the data loop.

In `@Content.Client/Humanoid/HumanoidAppearanceSystem.cs`:
- Around line 88-101: Update the sprite selection logic in SetBodyType so
HumanoidVisualLayers.Chest uses a dedicated chest sprite dictionary rather than
bodyTypeProto.ButtSprites, and HumanoidVisualLayers.Breast resolves only through
the appropriate BreastSprites entry instead of the generic bodyTypeProto.Sprites
fallback. Preserve the existing customization checks and size-based lookup
behavior.

In `@Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs`:
- Around line 2273-2290: UpdateBodyTypes so it checks whether _bodyTypes is
empty before calling _bodyTypes.First(). If no body types remain after filtering
by sex, skip SetBodyType and continue safely to UpdateBodyCustomizationControls;
preserve the existing selection behavior when the list is non-empty.

In
`@Content.Server.Database/Migrations/Postgres/20260729085256_AddBodyCustomization.cs`:
- Around line 8-9: Синхронизируйте временную метку Postgres-миграции
AddBodyCustomization с Sqlite-версией 20260729085231, переименовав файл и
соответствующий класс AddBodyCustomization при необходимости; сохраните
одинаковые имя и timestamp в обеих директориях.

In
`@Content.Server.Database/Migrations/Sqlite/20260729085231_AddBodyCustomization.cs`:
- Around line 13-25: Переименуйте идентификатор SQLite-миграции
AddBodyCustomization с timestamp 20260729085231 на 20260729085256, сохранив имя
миграции без изменений, чтобы он совпадал с PostgreSQL-миграцией и порядок
миграций был синхронизирован.

In `@Content.Server/Database/ServerDbBase.cs`:
- Around line 335-338: Replace the direct casts in the profile conversion chain
for WithBreastSize and WithButtSize with the same Enum.TryParse-based validation
and explicit fallback used by the nearby Sex, Gender, Erp, and Virginity fields.
Ensure invalid database values are normalized immediately to the established
fallback enum values before constructing the profile.

In `@Content.Shared/_Lust/Humanoid/BodyCustomization.cs`:
- Around line 5-24: Добавьте XML-документацию через /// <summary> для BreastSize
и ButtSize в Content.Shared/_Lust/Humanoid/BodyCustomization.cs, описав смысл их
значений; также задокументируйте поля BreastSize/ButtSize в
HumanoidAppearanceComponent.cs и свойства BreastSize/ButtSize в
HumanoidCharacterProfile.cs. Для всех трёх указанных мест используйте краткие
описания назначения API.

In `@Content.Shared/_Sunrise/BodyType.cs`:
- Around line 21-30: Add concise /// <summary> XML documentation for the new
DataField contracts SupportsBodyCustomization, ButtSprites, and
ShapeDisplacement in Content.Shared/_Sunrise/BodyType.cs lines 21-30, and for
SupportsBodyCustomization, BreastSprites, BreastDisplacements, and
ButtDisplacements in Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
lines 63-74; document each public field directly above its declaration.

In `@Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs`:
- Around line 86-99: Защитите fallback в блоке выбора BodyType вокруг
`speciesPrototype.BodyTypes.FirstOrDefault(...)`: не вызывайте `First()` на
пустом списке и обработайте ситуацию, когда все типы запрещены для текущего
пола, без необработанного исключения. Сохраните выбор первого подходящего типа
для валидных прототипов и примените аналогичную защиту в
`HumanoidProfileEditor.UpdateBodyTypes()`.

In `@Resources/Locale/en-US/_lust/_strings/preferences/body-customization.ftl`:
- Around line 9-14: Update the locale validation configuration or supported
suppression mechanism for humanoid-profile-editor-breast-size-aa, -a, -b, -c,
-d, and -dd so these intentionally identical cup-size abbreviations are accepted
as translated. Keep the en-US and ru-RU string values unchanged and ensure the
CI validator no longer reports them as untranslated.

In `@Resources/Locale/ru-RU/_lust/_strings/preferences/body-customization.ftl`:
- Around line 9-14: Update the Russian breast-size localization entries
humanoid-profile-editor-breast-size-aa through
humanoid-profile-editor-breast-size-dd so the locale validator recognizes them
as translated, while preserving the displayed cup-size meanings and the ordering
used by the en-US version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0082c045-abcf-46c0-9f00-5728a3b5800f

📥 Commits

Reviewing files that changed from the base of the PR and between c71339f and b6a9888.

⛔ Files ignored due to path filters (97)
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/head_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/l_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/r_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/torso_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/torso_f_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/torso_f_fit.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/head_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/l_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/r_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/torso_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/torso_m_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/torso_m_fit.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_0.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_1.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_2.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_3.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_4.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_5.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_6.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/size_7.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_a.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_aa.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_b.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_c.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_d.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_dd.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_e.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/breast_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/butt_athletic.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/butt_large.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/butt_standard.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_apple_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_apple_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_hourglass_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_hourglass_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_pear_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/shape_pear_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/head_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/l_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/r_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/torso_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/torso_f_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/torso_f_fit.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/head_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/l_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/r_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/torso_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/torso_m_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/torso_m_fit.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/head_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/l_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/r_leg_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/torso_f.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/torso_f_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/torso_f_fit.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/head_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/l_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/l_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/l_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/l_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/r_arm.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/r_foot.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/r_hand.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/r_leg.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/torso_m.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/torso_m_big.png is excluded by !**/*.png
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/torso_m_fit.png is excluded by !**/*.png
📒 Files selected for processing (36)
  • Content.Client/Clothing/ClientClothingSystem.cs
  • Content.Client/DisplacementMap/DisplacementMapSystem.cs
  • Content.Client/Humanoid/HumanoidAppearanceSystem.cs
  • Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
  • Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
  • Content.Server.Database/Migrations/Postgres/20260729085256_AddBodyCustomization.Designer.cs
  • Content.Server.Database/Migrations/Postgres/20260729085256_AddBodyCustomization.cs
  • Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs
  • Content.Server.Database/Migrations/Sqlite/20260729085231_AddBodyCustomization.Designer.cs
  • Content.Server.Database/Migrations/Sqlite/20260729085231_AddBodyCustomization.cs
  • Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs
  • Content.Server.Database/Model.cs
  • Content.Server/Database/ServerDbBase.cs
  • Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
  • Content.Shared/Humanoid/HumanoidVisualLayers.cs
  • Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs
  • Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
  • Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
  • Content.Shared/Preferences/HumanoidCharacterProfile.cs
  • Content.Shared/_Lust/Humanoid/BodyCustomization.cs
  • Content.Shared/_Sunrise/BodyType.cs
  • Resources/Locale/en-US/_lust/_strings/preferences/body-customization.ftl
  • Resources/Locale/ru-RU/_lust/_strings/preferences/body-customization.ftl
  • Resources/Prototypes/Entities/Mobs/Species/base.yml
  • Resources/Prototypes/Species/human.yml
  • Resources/Prototypes/_Lust/Shaders/body_displacement.yml
  • Resources/Prototypes/_Lust/Species/human_bodytypes.yml
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/apple_m.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/breast.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/displacement.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/hourglass_m.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear.rsi/meta.json
  • Resources/Textures/_Lust/Mobs/Species/Human/BodyTypes/pear_m.rsi/meta.json
  • Resources/Textures/_Lust/Shaders/body_displacement.swsl

Comment on lines +436 to 461
// Lust edit start - объединяем до четырёх карт в одном шейдере
if ((shapeDisplacement is not null
|| breastDisplacement is not null
|| buttDisplacement is not null)
&& _displacement.TryAddBodyCustomizationDisplacements(
null,
shapeDisplacement,
breastDisplacement,
buttDisplacement,
(equipee, sprite),
index,
key,
out var bodyDisplacementKeys))
{
foreach (var bodyDisplacementKey in bodyDisplacementKeys)
revealedLayers.Add(bodyDisplacementKey);
index += bodyDisplacementKeys.Count;
}
else if (displacementData is not null
&& _displacement.TryAddDisplacement(displacementData, (equipee, sprite), index, key, out var displacementKey))
{
revealedLayers.Add(displacementKey);
index++;
}
// Lust edit end
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

baseData всегда null — видо-специфичная посадка одежды теряется при активной кастомизации тела.

_displacement.TryAddBodyCustomizationDisplacements(null, shapeDisplacement, breastDisplacement, buttDisplacement, ...) — первым аргументом жёстко передан null, хотя чуть выше уже вычислен displacementData (карта посадки одежды под конкретный вид/пол). И шейдер (useBaseDisplacement), и сама функция явно спроектированы для комбинирования base+shape+breast+butt в одном вызове. Из-за этого для слотов, где кастомизация тела активна (jumpsuit, bra, pants/socks), стандартная видо-специфичная деформация одежды (inventory.Displacements/MaleDisplacements/FemaleDisplacements) просто отбрасывается — одежда рискует «не сесть» на модель.

🐛 Предлагаемый фикс
                 if ((shapeDisplacement is not null
                         || breastDisplacement is not null
                         || buttDisplacement is not null)
                     && _displacement.TryAddBodyCustomizationDisplacements(
-                        null,
+                        displacementData,
                         shapeDisplacement,
                         breastDisplacement,
                         buttDisplacement,
📝 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
// Lust edit start - объединяем до четырёх карт в одном шейдере
if ((shapeDisplacement is not null
|| breastDisplacement is not null
|| buttDisplacement is not null)
&& _displacement.TryAddBodyCustomizationDisplacements(
null,
shapeDisplacement,
breastDisplacement,
buttDisplacement,
(equipee, sprite),
index,
key,
out var bodyDisplacementKeys))
{
foreach (var bodyDisplacementKey in bodyDisplacementKeys)
revealedLayers.Add(bodyDisplacementKey);
index += bodyDisplacementKeys.Count;
}
else if (displacementData is not null
&& _displacement.TryAddDisplacement(displacementData, (equipee, sprite), index, key, out var displacementKey))
{
revealedLayers.Add(displacementKey);
index++;
}
// Lust edit end
}
// Lust edit start - объединяем до четырёх карт в одном шейдере
if ((shapeDisplacement is not null
|| breastDisplacement is not null
|| buttDisplacement is not null)
&& _displacement.TryAddBodyCustomizationDisplacements(
displacementData,
shapeDisplacement,
breastDisplacement,
buttDisplacement,
(equipee, sprite),
index,
key,
out var bodyDisplacementKeys))
{
foreach (var bodyDisplacementKey in bodyDisplacementKeys)
revealedLayers.Add(bodyDisplacementKey);
index += bodyDisplacementKeys.Count;
}
else if (displacementData is not null
&& _displacement.TryAddDisplacement(displacementData, (equipee, sprite), index, key, out var displacementKey))
{
revealedLayers.Add(displacementKey);
index++;
}
// Lust edit end
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/Clothing/ClientClothingSystem.cs` around lines 436 - 461, Pass
the already computed displacementData as the first argument to
TryAddBodyCustomizationDisplacements instead of null, while preserving the
existing shapeDisplacement, breastDisplacement, and buttDisplacement arguments
and fallback behavior. This ensures the base clothing displacement is combined
with body customization in the clothing layering flow.

Comment on lines +158 to +190
var shader = _prototype.Index(BodyDisplacedDrawShader).InstanceUnique();
shader.SetParameter("displacementSize", 127f);
foreach (var (prefix, mapData) in data)
shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", mapData is not null);
sprite.Comp.LayerSetShader(index, shader);

var insertionIndex = index;
foreach (var (prefix, mapData) in data)
{
if (mapData is null)
continue;

if (!mapData.SizeMaps.TryGetValue(layerSize, out var map)
&& !mapData.SizeMaps.TryGetValue(EyeManager.PixelsPerMeter, out map))
{
Log.Error($"BODY DISPLACEMENT: {prefix} map for {targetKey} has no 32x32 fallback");
continue;
}

var displacementLayer = _serialization.CreateCopy(map, notNullableOverride: true);
displacementLayer.CopyToShaderParameters = new PrototypeCopyToShaderParameters
{
LayerKey = targetKey,
ParameterTexture = $"{prefix}DisplacementMap",
ParameterUV = $"{prefix}DisplacementUV",
};

var displacementKey = BuildBodyDisplacementLayerKey(key, prefix);
_sprite.AddLayer(sprite.AsNullable(), displacementLayer, insertionIndex);
_sprite.LayerMapSet(sprite.AsNullable(), displacementKey, insertionIndex);
displacementKeys.Add(displacementKey);
insertionIndex++;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Флаги use*Displacement выставляются раньше, чем подтверждено наличие карты.

Цикл на строках 160-161 включает use{Prefix}Displacement для любого непустого DisplacementData?, а фактическое добавление слоя чуть ниже (170-175) может continue, если у карты нет ни layerSize, ни fallback 32×32 (Log.Error + continue). В этом случае шейдер получит useXDisplacement = true, но без забинженного xDisplacementMap/xDisplacementUV — в body_displacement.swsl это декодируется как смещение ≈(-1,-1)*127px, т.е. заметный визуальный артефакт вместо тихого отключения слоя. Для сравнения, старый TryAddDisplacement в этом же файле делает ранний return false при отсутствии карты 32×32, не успевая тронуть шейдер.

🐛 Предлагаемый фикс — выставлять флаг только после успешного резолва карты
         var shader = _prototype.Index(BodyDisplacedDrawShader).InstanceUnique();
         shader.SetParameter("displacementSize", 127f);
-        foreach (var (prefix, mapData) in data)
-            shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", mapData is not null);
         sprite.Comp.LayerSetShader(index, shader);

         var insertionIndex = index;
         foreach (var (prefix, mapData) in data)
         {
             if (mapData is null)
+            {
+                shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", false);
                 continue;
+            }

             if (!mapData.SizeMaps.TryGetValue(layerSize, out var map)
                 && !mapData.SizeMaps.TryGetValue(EyeManager.PixelsPerMeter, out map))
             {
                 Log.Error($"BODY DISPLACEMENT: {prefix} map for {targetKey} has no 32x32 fallback");
+                shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", false);
                 continue;
             }

+            shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", true);
             var displacementLayer = _serialization.CreateCopy(map, notNullableOverride: true);
📝 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
var shader = _prototype.Index(BodyDisplacedDrawShader).InstanceUnique();
shader.SetParameter("displacementSize", 127f);
foreach (var (prefix, mapData) in data)
shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", mapData is not null);
sprite.Comp.LayerSetShader(index, shader);
var insertionIndex = index;
foreach (var (prefix, mapData) in data)
{
if (mapData is null)
continue;
if (!mapData.SizeMaps.TryGetValue(layerSize, out var map)
&& !mapData.SizeMaps.TryGetValue(EyeManager.PixelsPerMeter, out map))
{
Log.Error($"BODY DISPLACEMENT: {prefix} map for {targetKey} has no 32x32 fallback");
continue;
}
var displacementLayer = _serialization.CreateCopy(map, notNullableOverride: true);
displacementLayer.CopyToShaderParameters = new PrototypeCopyToShaderParameters
{
LayerKey = targetKey,
ParameterTexture = $"{prefix}DisplacementMap",
ParameterUV = $"{prefix}DisplacementUV",
};
var displacementKey = BuildBodyDisplacementLayerKey(key, prefix);
_sprite.AddLayer(sprite.AsNullable(), displacementLayer, insertionIndex);
_sprite.LayerMapSet(sprite.AsNullable(), displacementKey, insertionIndex);
displacementKeys.Add(displacementKey);
insertionIndex++;
}
var shader = _prototype.Index(BodyDisplacedDrawShader).InstanceUnique();
shader.SetParameter("displacementSize", 127f);
sprite.Comp.LayerSetShader(index, shader);
var insertionIndex = index;
foreach (var (prefix, mapData) in data)
{
if (mapData is null)
{
shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", false);
continue;
}
if (!mapData.SizeMaps.TryGetValue(layerSize, out var map)
&& !mapData.SizeMaps.TryGetValue(EyeManager.PixelsPerMeter, out map))
{
Log.Error($"BODY DISPLACEMENT: {prefix} map for {targetKey} has no 32x32 fallback");
shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", false);
continue;
}
shader.SetParameter($"use{char.ToUpperInvariant(prefix[0])}{prefix[1..]}Displacement", true);
var displacementLayer = _serialization.CreateCopy(map, notNullableOverride: true);
displacementLayer.CopyToShaderParameters = new PrototypeCopyToShaderParameters
{
LayerKey = targetKey,
ParameterTexture = $"{prefix}DisplacementMap",
ParameterUV = $"{prefix}DisplacementUV",
};
var displacementKey = BuildBodyDisplacementLayerKey(key, prefix);
_sprite.AddLayer(sprite.AsNullable(), displacementLayer, insertionIndex);
_sprite.LayerMapSet(sprite.AsNullable(), displacementKey, insertionIndex);
displacementKeys.Add(displacementKey);
insertionIndex++;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/DisplacementMap/DisplacementMapSystem.cs` around lines 158 -
190, Set each use{Prefix}Displacement shader flag only after successfully
resolving the corresponding map in the displacement-layer loop. Ensure missing
layerSize and 32x32 fallback maps leave the flag disabled while preserving the
existing error and continue behavior, and remove or avoid the earlier
unconditional flag assignment in the data loop.

Comment on lines +88 to +101
// Lust edit start - грудь и ягодицы выбираются независимо от формы тела
var id = defaultId;
if (bodyTypeProto.SupportsBodyCustomization
&& key == HumanoidVisualLayers.Chest
&& bodyTypeProto.ButtSprites.TryGetValue(component.ButtSize, out var buttSprite))
{
id = buttSprite;
}
else if (bodyTypeProto.SupportsBodyCustomization
&& key == HumanoidVisualLayers.Breast
&& speciesProto.BreastSprites.TryGetValue(component.BreastSize, out var breastSprite))
{
id = breastSprite;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "Chest|Breast|ButtSprites|BreastSprites" -g '*.cs' Content.Shared/Humanoid Content.Shared/_Sunrise 2>/dev/null

Repository: space-sunrise/lust-station

Length of output: 1780


🏁 Script executed:

#!/bin/bash
set -e

echo "== HumanoidVisualLayers.cs =="
cat -n Content.Shared/Humanoid/HumanoidVisualLayers.cs | sed -n '1,80p'

echo
echo "== HumanoidVisualLayersExtension.cs relevant =="
cat -n Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs | sed -n '1,120p'

echo
echo "== SharedHumanoidAppearanceSystem.cs referenced section =="
cat -n Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs | sed -n '70,115p'

echo
echo "== BodyType extension around sprites =="
cat -n Content.Shared/_Sunrise/BodyType.cs | sed -n '1,70p'

echo
echo "== search HumanoidVisualLayers usage in Shared =="
rg -n "HumanoidVisualLayers\\.(Chest|Breast|Butt)|ChestSprites|ButtSprites|BreastSprites" Content.Shared -g '*.cs'

Repository: space-sunrise/lust-station

Length of output: 11616


Исправить выбор спрайтов для слоёв груди.

HumanoidVisualLayers.Chest соответствует торсу (BodyPartType.Torso), а HumanoidVisualLayers.Breast — добавлен как отдельный слой груди. В SetBodyType() слой Chest не должен принимать bodyTypeProto.ButtSprites, а грудь нельзя отображать из общего базового спрайта bodyTypeProto.Sprites; добавьте/используйте отдельную справочник для Chest и выберите корректный спрайт для BreastSprites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/Humanoid/HumanoidAppearanceSystem.cs` around lines 88 - 101,
Update the sprite selection logic in SetBodyType so HumanoidVisualLayers.Chest
uses a dedicated chest sprite dictionary rather than bodyTypeProto.ButtSprites,
and HumanoidVisualLayers.Breast resolves only through the appropriate
BreastSprites entry instead of the generic bodyTypeProto.Sprites fallback.
Preserve the existing customization checks and size-based lookup behavior.

Comment on lines 2273 to 2290
private void UpdateBodyTypes()
{
if (Profile is null)
return;

CBodyTypesButton.Clear();
var species = _prototypeManager.Index(Profile.Species);
var sex = Profile.Sex;
_bodyTypes = species.BodyTypes.Select(protoId => _prototypeManager.Index<BodyTypePrototype>(protoId))
.Where(proto => !proto.SexRestrictions.Contains(sex.ToString()))
.ToList();

for (var i = 0; i < _bodyTypes.Count; i++)
{
CBodyTypesButton.AddItem(Loc.GetString(_bodyTypes[i].Name), i);
}

if (!_bodyTypes.Select(proto => proto.ID).Contains(Profile.BodyType))
{
SetBodyType(_bodyTypes.First().ID);
}

CBodyTypesButton.Select(_bodyTypes.FindIndex(x => x.ID == Profile.BodyType));
UpdateBodyCustomizationControls(species); // Lust added
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

_bodyTypes.First() без проверки на пустой список.

Если после фильтрации species.BodyTypes по SexRestrictions для текущего пола не останется ни одного элемента (например, вид сконфигурирован неверно), _bodyTypes.First() бросит InvalidOperationException прямо в редакторе профиля — до того, как сработает проверка _bodyTypes.Count == 0 в UpdateBodyCustomizationControls, которая идёт уже после.

🛡️ Пример защиты
             _bodyTypes = species.BodyTypes.Select(protoId => _prototypeManager.Index<BodyTypePrototype>(protoId))
                 .Where(proto => !proto.SexRestrictions.Contains(sex.ToString()))
                 .ToList();

-            if (!_bodyTypes.Select(proto => proto.ID).Contains(Profile.BodyType))
+            if (_bodyTypes.Count == 0)
+            {
+                _sawmill.Error($"У вида {species.ID} нет BodyTypes, доступных для пола {sex}");
+            }
+            else if (!_bodyTypes.Select(proto => proto.ID).Contains(Profile.BodyType))
             {
                 SetBodyType(_bodyTypes.First().ID);
             }

Тот же паттерн — в SharedHumanoidAppearanceSystem.SetBodyType, см. консолидированный комментарий.

📝 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
private void UpdateBodyTypes()
{
if (Profile is null)
return;
CBodyTypesButton.Clear();
var species = _prototypeManager.Index(Profile.Species);
var sex = Profile.Sex;
_bodyTypes = species.BodyTypes.Select(protoId => _prototypeManager.Index<BodyTypePrototype>(protoId))
.Where(proto => !proto.SexRestrictions.Contains(sex.ToString()))
.ToList();
for (var i = 0; i < _bodyTypes.Count; i++)
{
CBodyTypesButton.AddItem(Loc.GetString(_bodyTypes[i].Name), i);
}
if (!_bodyTypes.Select(proto => proto.ID).Contains(Profile.BodyType))
{
SetBodyType(_bodyTypes.First().ID);
}
CBodyTypesButton.Select(_bodyTypes.FindIndex(x => x.ID == Profile.BodyType));
UpdateBodyCustomizationControls(species); // Lust added
}
private void UpdateBodyTypes()
{
if (Profile is null)
return;
var species = _prototypeManager.Index(Profile.Species);
var sex = Profile.Sex;
_bodyTypes = species.BodyTypes.Select(protoId => _prototypeManager.Index<BodyTypePrototype>(protoId))
.Where(proto => !proto.SexRestrictions.Contains(sex.ToString()))
.ToList();
if (_bodyTypes.Count == 0)
{
_sawmill.Error($"У вида {species.ID} нет BodyTypes, доступных для пола {sex}");
}
else if (!_bodyTypes.Select(proto => proto.ID).Contains(Profile.BodyType))
{
SetBodyType(_bodyTypes.First().ID);
}
UpdateBodyCustomizationControls(species); // Lust added
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs` around lines 2273 -
2290, UpdateBodyTypes so it checks whether _bodyTypes is empty before calling
_bodyTypes.First(). If no body types remain after filtering by sex, skip
SetBodyType and continue safely to UpdateBodyCustomizationControls; preserve the
existing selection behavior when the list is non-empty.

Comment on lines +8 to +9
public partial class AddBodyCustomization : Migration
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Таймштамп миграции не совпадает с Sqlite-версией.

Postgres-миграция имеет метку 20260729085256, а Sqlite-контрагент (20260729085231_AddBodyCustomization) — 20260729085231. Имя класса совпадает, но метки отличаются на 25 секунд.

As per coding guidelines: "Migration files must be created with matching timestamps and names in both the Migrations/Sqlite and Migrations/Postgres directories to keep database schemas in sync across both engines."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Content.Server.Database/Migrations/Postgres/20260729085256_AddBodyCustomization.cs`
around lines 8 - 9, Синхронизируйте временную метку Postgres-миграции
AddBodyCustomization с Sqlite-версией 20260729085231, переименовав файл и
соответствующий класс AddBodyCustomization при необходимости; сохраните
одинаковые имя и timestamp в обеих директориях.

Source: Coding guidelines

Comment on lines +5 to +24
[Serializable, NetSerializable]
public enum BreastSize : byte
{
AA,
A,
B,
C,
D,
DD,
E,
F,
}

[Serializable, NetSerializable]
public enum ButtSize : byte
{
Athletic,
Standard,
Large,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Новый публичный API BreastSize/ButtSize без XML-документации. Во всех трёх местах, где определяется или объявляется новая публичная кастомизация тела, отсутствуют /// <summary>-комментарии — единая причина, единое исправление.

  • Content.Shared/_Lust/Humanoid/BodyCustomization.cs#L5-L24: добавить /// <summary> к enum BreastSize и ButtSize, кратко описав семантику значений.
  • Content.Shared/Humanoid/HumanoidAppearanceComponent.cs#L111-L118: добавить /// <summary> к полям BreastSize/ButtSize компонента.
  • Content.Shared/Preferences/HumanoidCharacterProfile.cs#L115-L122: добавить /// <summary> к [DataField]-свойствам BreastSize/ButtSize профиля.

As per coding guidelines: "Each public field in a component must have XML documentation with a ///

comment explaining its purpose" and "Use /// <summary> as the main documentation format in C# and SWSL code".

📍 Affects 3 files
  • Content.Shared/_Lust/Humanoid/BodyCustomization.cs#L5-L24 (this comment)
  • Content.Shared/Humanoid/HumanoidAppearanceComponent.cs#L111-L118
  • Content.Shared/Preferences/HumanoidCharacterProfile.cs#L115-L122
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/_Lust/Humanoid/BodyCustomization.cs` around lines 5 - 24,
Добавьте XML-документацию через /// <summary> для BreastSize и ButtSize в
Content.Shared/_Lust/Humanoid/BodyCustomization.cs, описав смысл их значений;
также задокументируйте поля BreastSize/ButtSize в HumanoidAppearanceComponent.cs
и свойства BreastSize/ButtSize в HumanoidCharacterProfile.cs. Для всех трёх
указанных мест используйте краткие описания назначения API.

Source: Coding guidelines

Comment on lines +21 to +30

// Lust added start - независимая кастомизация фигуры человека
[DataField]
public bool SupportsBodyCustomization;

[DataField]
public Dictionary<ButtSize, string> ButtSprites = new();

[DataField]
public DisplacementData? ShapeDisplacement;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Добавьте XML-документацию ко всем новым DataField-контрактам.

В обоих прототипах добавлены публичные сериализуемые поля без /// <summary>. Добавьте краткие описания каждого нового поля:

  • Content.Shared/_Sunrise/BodyType.cs#L21-L30: документировать SupportsBodyCustomization, ButtSprites и ShapeDisplacement.
  • Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs#L63-L74: документировать SupportsBodyCustomization, BreastSprites, BreastDisplacements и ButtDisplacements.

As per coding guidelines: публичные API и DataField-контракты должны документироваться через /// <summary>.

📍 Affects 2 files
  • Content.Shared/_Sunrise/BodyType.cs#L21-L30 (this comment)
  • Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs#L63-L74
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/_Sunrise/BodyType.cs` around lines 21 - 30, Add concise ///
<summary> XML documentation for the new DataField contracts
SupportsBodyCustomization, ButtSprites, and ShapeDisplacement in
Content.Shared/_Sunrise/BodyType.cs lines 21-30, and for
SupportsBodyCustomization, BreastSprites, BreastDisplacements, and
ButtDisplacements in Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
lines 63-74; document each public field directly above its declaration.

Source: Coding guidelines

Comment on lines +86 to +99
if (speciesPrototype.BodyTypes.Contains(bodyType)
&& _proto.TryIndex<BodyTypePrototype>(bodyType, out var bodyTypePrototype)
&& !bodyTypePrototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
{
humanoid.BodyType = bodyType;
}
else
humanoid.BodyType = speciesPrototype.BodyTypes.First();
{
// Lust edit - при смене пола Futanari получает женский набор, Male — мужской
humanoid.BodyType = speciesPrototype.BodyTypes
.FirstOrDefault(id => _proto.TryIndex<BodyTypePrototype>(id, out var prototype)
&& !prototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
?? speciesPrototype.BodyTypes.First();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Незащищённый .First() в конце фолбэка может уронить клиента/сервер.

Если для humanoid.Sex все элементы speciesPrototype.BodyTypes окажутся в SexRestrictions (или сам список пуст из-за некорректного прототипа вида), FirstOrDefault(...) вернёт null, а следующий speciesPrototype.BodyTypes.First() бросит InvalidOperationException — необработанное исключение при загрузке профиля/смене пола.

🛡️ Пример защиты
-            humanoid.BodyType = speciesPrototype.BodyTypes
-                .FirstOrDefault(id => _proto.TryIndex<BodyTypePrototype>(id, out var prototype)
-                    && !prototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
-                ?? speciesPrototype.BodyTypes.First();
+            var fallback = speciesPrototype.BodyTypes
+                .FirstOrDefault(id => _proto.TryIndex<BodyTypePrototype>(id, out var prototype)
+                    && !prototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
+                ?? speciesPrototype.BodyTypes.FirstOrDefault();
+            if (fallback == default)
+            {
+                Log.Error($"SetBodyType: у вида {humanoid.Species} нет доступных BodyTypes");
+                return;
+            }
+            humanoid.BodyType = fallback;

Тот же паттерн повторяется в HumanoidProfileEditor.xaml.csUpdateBodyTypes(), см. консолидированный комментарий.

📝 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
if (speciesPrototype.BodyTypes.Contains(bodyType)
&& _proto.TryIndex<BodyTypePrototype>(bodyType, out var bodyTypePrototype)
&& !bodyTypePrototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
{
humanoid.BodyType = bodyType;
}
else
humanoid.BodyType = speciesPrototype.BodyTypes.First();
{
// Lust edit - при смене пола Futanari получает женский набор, Male — мужской
humanoid.BodyType = speciesPrototype.BodyTypes
.FirstOrDefault(id => _proto.TryIndex<BodyTypePrototype>(id, out var prototype)
&& !prototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
?? speciesPrototype.BodyTypes.First();
}
if (speciesPrototype.BodyTypes.Contains(bodyType)
&& _proto.TryIndex<BodyTypePrototype>(bodyType, out var bodyTypePrototype)
&& !bodyTypePrototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
{
humanoid.BodyType = bodyType;
}
else
{
// Lust edit - при смене пола Futanari получает женский набор, Male — мужской
var fallback = speciesPrototype.BodyTypes
.FirstOrDefault(id => _proto.TryIndex<BodyTypePrototype>(id, out var prototype)
&& !prototype.SexRestrictions.Contains(humanoid.Sex.ToString()))
?? speciesPrototype.BodyTypes.FirstOrDefault();
if (fallback == default)
{
Log.Error($"SetBodyType: у вида {humanoid.Species} нет доступных BodyTypes");
return;
}
humanoid.BodyType = fallback;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs` around lines 86 -
99, Защитите fallback в блоке выбора BodyType вокруг
`speciesPrototype.BodyTypes.FirstOrDefault(...)`: не вызывайте `First()` на
пустом списке и обработайте ситуацию, когда все типы запрещены для текущего
пола, без необработанного исключения. Сохраните выбор первого подходящего типа
для валидных прототипов и примените аналогичную защиту в
`HumanoidProfileEditor.UpdateBodyTypes()`.

Comment on lines +9 to +14
humanoid-profile-editor-breast-size-aa = AA
humanoid-profile-editor-breast-size-a = A
humanoid-profile-editor-breast-size-b = B
humanoid-profile-editor-breast-size-c = C
humanoid-profile-editor-breast-size-d = D
humanoid-profile-editor-breast-size-dd = DD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Locale Validator валит пайплайн: строки помечены как «untranslated».

humanoid-profile-editor-breast-size-aa/a/b/c/d/dd — идентичны по содержимому en-US и ru-RU версии (латинские буквы размеров чашки одинаковы в обоих языках), из-за чего валидатор считает их непереведёнными. Нужно либо явно пометить эти строки как допустимо идентичные (если такой механизм в валидаторе есть), либо согласовать с CI-конфигом исключение для таких аббревиатур — иначе PR не пройдёт пайплайн.

🧰 Tools
🪛 GitHub Actions: Locale Validator / Validate Locales

[error] 9-9: Untranslated string: "humanoid-profile-editor-breast-size-aa". humanoid-profile-editor-breast-size-aa = AA


[error] 10-10: Untranslated string: "humanoid-profile-editor-breast-size-a". humanoid-profile-editor-breast-size-a = A


[error] 11-11: Untranslated string: "humanoid-profile-editor-breast-size-b". humanoid-profile-editor-breast-size-b = B


[error] 12-12: Untranslated string: "humanoid-profile-editor-breast-size-c". humanoid-profile-editor-breast-size-c = C


[error] 13-13: Untranslated string: "humanoid-profile-editor-breast-size-d". humanoid-profile-editor-breast-size-d = D


[error] 14-14: Untranslated string: "humanoid-profile-editor-breast-size-dd". humanoid-profile-editor-breast-size-dd = DD

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resources/Locale/en-US/_lust/_strings/preferences/body-customization.ftl`
around lines 9 - 14, Update the locale validation configuration or supported
suppression mechanism for humanoid-profile-editor-breast-size-aa, -a, -b, -c,
-d, and -dd so these intentionally identical cup-size abbreviations are accepted
as translated. Keep the en-US and ru-RU string values unchanged and ensure the
CI validator no longer reports them as untranslated.

Source: Pipeline failures

Comment on lines +9 to +14
humanoid-profile-editor-breast-size-aa = AA
humanoid-profile-editor-breast-size-a = A
humanoid-profile-editor-breast-size-b = B
humanoid-profile-editor-breast-size-c = C
humanoid-profile-editor-breast-size-d = D
humanoid-profile-editor-breast-size-dd = DD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Locale Validator валит пайплайн: строки помечены как «untranslated» (см. также en-US версию).

Причина та же: аббревиатуры размеров чашки (AA, A, B, C, D, DD) идентичны в обоих языках, и валидатор трактует это как непереведённый текст.

🧰 Tools
🪛 GitHub Actions: Locale Validator / Validate Locales

[error] 9-9: Untranslated string: "humanoid-profile-editor-breast-size-aa". humanoid-profile-editor-breast-size-aa = AA


[error] 10-10: Untranslated string: "humanoid-profile-editor-breast-size-a". humanoid-profile-editor-breast-size-a = A


[error] 11-11: Untranslated string: "humanoid-profile-editor-breast-size-b". humanoid-profile-editor-breast-size-b = B


[error] 12-12: Untranslated string: "humanoid-profile-editor-breast-size-c". humanoid-profile-editor-breast-size-c = C


[error] 13-13: Untranslated string: "humanoid-profile-editor-breast-size-d". humanoid-profile-editor-breast-size-d = D


[error] 14-14: Untranslated string: "humanoid-profile-editor-breast-size-dd". humanoid-profile-editor-breast-size-dd = DD

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resources/Locale/ru-RU/_lust/_strings/preferences/body-customization.ftl`
around lines 9 - 14, Update the Russian breast-size localization entries
humanoid-profile-editor-breast-size-aa through
humanoid-profile-editor-breast-size-dd so the locale validator recognizes them
as translated, while preserving the displayed cup-size meanings and the ordering
used by the en-US version.

Source: Pipeline failures

@github-actions

Copy link
Copy Markdown
Contributor

Этот PR содержит конфликты, пожалуйста, разрешите их, прежде чем мы сможем его оценить.

@matizzze

Copy link
Copy Markdown
Member

богу стыдно

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