diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f815e81 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig for RimWorld Mod + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.cs] +# Allow underscores in type names (common in RimWorld modding for Harmony patches) +dotnet_diagnostic.IDE1006.severity = none + +# C# formatting +indent_style = space +indent_size = 4 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56326f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Build results +Assemblies/*.dll +Assemblies/*.pdb + +# C# build artifacts +Source/**/bin/ +Source/**/obj/ +Source/**/.vs/ + +# Rider +.idea/ + +# Visual Studio +*.suo +*.user +*.userosscache +*.sln.docstates +.vs/ + +# ReSharper +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# Backup files +*.bak +*~ + diff --git a/AIImages.sln b/AIImages.sln new file mode 100644 index 0000000..a283d0d --- /dev/null +++ b/AIImages.sln @@ -0,0 +1,29 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{B8EFCA5F-814F-285C-A8CB-F00F14650265}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AIImages", "Source\AIImages\AIImages.csproj", "{4034C9F8-9E91-BB9A-74AF-03434F728EA7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4034C9F8-9E91-BB9A-74AF-03434F728EA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4034C9F8-9E91-BB9A-74AF-03434F728EA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4034C9F8-9E91-BB9A-74AF-03434F728EA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4034C9F8-9E91-BB9A-74AF-03434F728EA7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4034C9F8-9E91-BB9A-74AF-03434F728EA7} = {B8EFCA5F-814F-285C-A8CB-F00F14650265} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EDE3FCE6-7C4C-44E5-B099-32381852D4AF} + EndGlobalSection +EndGlobal diff --git a/About/About.xml b/About/About.xml new file mode 100644 index 0000000..8fdffcf --- /dev/null +++ b/About/About.xml @@ -0,0 +1,33 @@ + + + AI Images + mrleo1nid + Mrleo1nid.aiimages + +
  • 1.6
  • +
    + Adds a button to pawns that opens a custom window. + +
  • + rim.job.world + RimJobWorld + https://www.loverslab.com/topic/110270-mod-rimjobworld/ +
  • +
  • + brrainz.harmony + Harmony + steam://url/CommunityFilePage/2009463077 + https://github.com/pardeike/HarmonyRimWorld/releases/latest +
  • +
    + +
  • Ludeon.RimWorld
  • +
  • Ludeon.RimWorld.Royalty
  • +
  • Ludeon.RimWorld.Ideology
  • +
  • Ludeon.RimWorld.Biotech
  • +
  • Ludeon.RimWorld.Anomaly
  • +
  • UnlimitedHugs.HugsLib
  • +
  • brrainz.harmony
  • +
  • rim.job.world
  • +
    +
    diff --git a/About/Preview.png b/About/Preview.png new file mode 100644 index 0000000..0f20323 --- /dev/null +++ b/About/Preview.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg== diff --git a/DEBUGGING_QUICKSTART_RU.txt b/DEBUGGING_QUICKSTART_RU.txt new file mode 100644 index 0000000..44b6e82 --- /dev/null +++ b/DEBUGGING_QUICKSTART_RU.txt @@ -0,0 +1,170 @@ +╔══════════════════════════════════════════════════════════════════════════╗ +║ 🐛 БЫСТРЫЙ СТАРТ - ОТЛАДКА МОДА ║ +╚══════════════════════════════════════════════════════════════════════════╝ + + +═══════════════════════════════════════════════════════════════════════════ + СПОСОБ 1: ATTACH TO PROCESS (С BREAKPOINTS) ⭐ +═══════════════════════════════════════════════════════════════════════════ + + ✅ Проект уже настроен для отладки! + ✅ .pdb файл создаётся автоматически при сборке + + 📋 ШАГИ: + + 1️⃣ Откройте проект в Visual Studio или Rider: + Source/AIImages/AIImages.csproj + + 2️⃣ Соберите в Debug режиме (уже собрано): + cd Source/AIImages + dotnet build -c Debug + + 3️⃣ Запустите RimWorld (через Steam или напрямую) + + 4️⃣ В Visual Studio: + • Debug → Attach to Process... (Ctrl+Alt+P) + • Найдите "RimWorldWin64.exe" + • Нажмите "Attach" + + В Rider: + • Run → Attach to Process... + • Найдите "RimWorldWin64" + • Нажмите OK + + 5️⃣ Откройте файл с кодом (например PawnGizmoPatch.cs) + + 6️⃣ Поставьте breakpoint: + • Кликните слева от номера строки + • Появится красная точка 🔴 + + 7️⃣ В игре выберите пешку + → Код остановится на breakpoint! + → Можно смотреть значения переменных + + +═══════════════════════════════════════════════════════════════════════════ + СПОСОБ 2: ЛОГИРОВАНИЕ (ПРОСТОЙ) 📝 +═══════════════════════════════════════════════════════════════════════════ + + 1️⃣ Добавьте в код Log.Message(): + + Log.Message("[AI Images] Pawn selected: " + __instance.Name); + + 2️⃣ Соберите проект: + dotnet build + + 3️⃣ Запустите RimWorld + + 4️⃣ В игре нажмите Ctrl+F12 → окно логов + + 5️⃣ Выберите пешку → увидите сообщение в логе! + + +═══════════════════════════════════════════════════════════════════════════ + ГОТОВЫЕ ПРИМЕРЫ ДЛЯ ВСТАВКИ +═══════════════════════════════════════════════════════════════════════════ + + 📍 В PawnGizmoPatch.cs (строка 16): + + Log.Message($"[AI Images] GetGizmos for: {__instance.Name}"); + + 📍 В Window_AIImage.cs (в DoWindowContents, строка 25): + + Log.Message($"[AI Images] Drawing window for: {pawn.Name}"); + + 📍 При нажатии кнопки (PawnGizmoPatch.cs, строка 34): + + Log.Message("[AI Images] Button clicked!"); + + +═══════════════════════════════════════════════════════════════════════════ + ГДЕ НАХОДЯТСЯ ЛОГИ +═══════════════════════════════════════════════════════════════════════════ + + В игре: + Ctrl+F12 → окно с логами в реальном времени + + Файл лога: + C:\Users\mrleo1nid\AppData\LocalLow\Ludeon Studios\ + RimWorld by Ludeon Studios\Player.log + + +═══════════════════════════════════════════════════════════════════════════ + ПОЛЕЗНЫЕ КОМАНДЫ +═══════════════════════════════════════════════════════════════════════════ + + Сборка в Debug (с .pdb): + cd Source/AIImages + dotnet build -c Debug + + Сборка в Release (без .pdb, для публикации): + dotnet build -c Release + + Очистка и пересборка: + dotnet clean + dotnet build -c Debug + + +═══════════════════════════════════════════════════════════════════════════ + СОВЕТЫ +═══════════════════════════════════════════════════════════════════════════ + + ✅ Используйте префикс [AI Images] в логах - легче искать + + ✅ Dev Mode в игре (Options → Settings → Development mode) + Даёт доступ к Debug Actions и Debug Inspector + + ✅ Логируйте важные моменты: + • Когда метод вызывается + • Значения переменных + • Успех/неудачу операций + + ✅ Try-Catch для отлова ошибок: + + try { + // Ваш код + } catch (Exception ex) { + Log.Error($"[AI Images] Error: {ex}"); + } + + +═══════════════════════════════════════════════════════════════════════════ + TROUBLESHOOTING +═══════════════════════════════════════════════════════════════════════════ + + ❓ Breakpoint не срабатывает? + → Убедитесь, что собрано в Debug (не Release) + → Проверьте, что .pdb файл есть в Assemblies/ + → Код действительно выполняется? (добавьте Log.Message) + + ❓ Не видите свой мод в Process List? + → Убедитесь, что мод загружен в игре + → Попробуйте перезапустить игру + + ❓ "Symbols not loaded"? + → Удалите все из Assemblies/ + → Пересоберите: dotnet clean && dotnet build -c Debug + + +═══════════════════════════════════════════════════════════════════════════ + ДОПОЛНИТЕЛЬНАЯ ДОКУМЕНТАЦИЯ +═══════════════════════════════════════════════════════════════════════════ + + 📖 Полная инструкция: DEBUGGING_RU.md + 📖 Примеры кода: DEV_CHEATSHEET.txt + + +╔══════════════════════════════════════════════════════════════════════════╗ +║ ✅ ВСЁ ГОТОВО ДЛЯ ОТЛАДКИ! ║ +║ ║ +║ Файлы в Assemblies/: ║ +║ • AIImages.dll ✅ ║ +║ • AIImages.pdb ✅ (отладочные символы) ║ +║ • 0Harmony.dll ✅ ║ +║ ║ +║ Запустите RimWorld и подключите отладчик! ║ +╚══════════════════════════════════════════════════════════════════════════╝ + + + Удачной отладки! 🐛→✨ + diff --git a/DEBUGGING_RU.md b/DEBUGGING_RU.md new file mode 100644 index 0000000..3d846c5 --- /dev/null +++ b/DEBUGGING_RU.md @@ -0,0 +1,444 @@ +# 🐛 Отладка мода RimWorld + +## Способы отладки + +### 1. 🔴 Attach to Process (Рекомендуется) + +Это самый удобный способ для отладки модов RimWorld. + +#### Visual Studio + +1. **Измените .csproj для включения отладочных символов:** + +```xml + + net472 + latest + ..\..\Assemblies + false + portable + true + +``` + +2. **Соберите проект в Debug режиме:** +```bash +cd Source/AIImages +dotnet build -c Debug +``` + +3. **Запустите RimWorld** + +4. **В Visual Studio:** + - Меню: `Debug` → `Attach to Process...` (или `Ctrl+Alt+P`) + - Найдите процесс `RimWorldWin64.exe` + - Нажмите `Attach` + +5. **Установите breakpoint:** + - Откройте файл с кодом (например, `PawnGizmoPatch.cs`) + - Кликните слева от номера строки (появится красная точка) + +6. **Триггерьте код в игре:** + - Выберите пешку → код остановится на breakpoint! + +#### JetBrains Rider + +1. **Настройте Run Configuration:** + - `Run` → `Edit Configurations...` + - `+` → `.NET Executable` + - Имя: `RimWorld Debug` + - Exe path: `D:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin64.exe` + - Working directory: `D:\SteamLibrary\steamapps\common\RimWorld` + +2. **Соберите проект в Debug:** +```bash +dotnet build -c Debug +``` + +3. **Запустите через Rider:** + - `Run` → `Debug 'RimWorld Debug'` (или `Shift+F9`) + - Игра запустится с подключенным отладчиком + +4. **Breakpoints:** + - Работают так же, как в Visual Studio + +--- + +### 2. 📝 Логирование (Простой способ) + +Если не нужна полная отладка, используйте логирование. + +#### Добавьте в код: + +```csharp +using Verse; + +// Информационное сообщение +Log.Message("[AI Images] Pawn selected: " + __instance.Name); + +// Предупреждение +Log.Warning("[AI Images] Something suspicious"); + +// Ошибка +Log.Error("[AI Images] Something went wrong!"); +``` + +#### Просмотр логов: + +**В игре:** +- Нажмите `Ctrl+F12` → откроется окно логов +- Ищите строки с `[AI Images]` + +**Файл лога:** +- Windows: `C:\Users\YourName\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Player.log` +- Или в папке RimWorld: `RimWorld\LogOutput\` + +--- + +### 3. 🎮 Dev Mode в игре + +Включите режим разработчика для дополнительных инструментов. + +#### Как включить: + +1. Запустите RimWorld +2. `Options` → `Settings` → включите `Development mode` +3. Перезапустите игру + +#### Что даёт Dev Mode: + +**Debug Actions Menu** (верхняя панель): +- Можно создавать своих debug actions +- Быстрое тестирование функций + +**Debug Inspector** (`Top bar` → `Tools` → `Debug inspector`): +- Кликните на любой объект +- Просмотр всех полей и свойств в реальном времени + +**Debug Log** (`Ctrl+F12`): +- Расширенная информация +- Трассировка стека + +#### Создание Debug Action: + +```csharp +using Verse; +using RimWorld; + +namespace AIImages +{ + public static class DebugActions + { + [DebugAction( + "AI Images", + "Test Window", + actionType = DebugActionType.ToolMapForPawns, + allowedGameStates = AllowedGameStates.PlayingOnMap + )] + public static void TestWindow(Pawn pawn) + { + Find.WindowStack.Add(new Window_AIImage(pawn)); + Log.Message($"[AI Images] Opened window for {pawn.Name}"); + } + } +} +``` + +Теперь в игре: +- Верхняя панель → `Debug Actions` +- Категория `AI Images` → `Test Window` +- Кликните на пешку → окно откроется + +--- + +### 4. 🔍 Продвинутая отладка + +#### dnSpy - декомпилятор с отладчиком + +**Установка:** +1. Скачайте dnSpy: https://github.com/dnSpy/dnSpy/releases +2. Распакуйте и запустите `dnSpy.exe` + +**Использование:** +1. `File` → `Open` → выберите `RimWorld\RimWorldWin64_Data\Managed\Assembly-CSharp.dll` +2. Изучайте код RimWorld! +3. Можно ставить breakpoints прямо в коде игры + +**Attach to Process:** +1. Запустите RimWorld +2. В dnSpy: `Debug` → `Attach to Process` +3. Выберите `RimWorldWin64.exe` +4. Ставьте breakpoints в коде игры или вашего мода + +--- + +## ⚙️ Настройка проекта для отладки + +### Обновите AIImages.csproj: + +```xml + + + net472 + latest + ..\..\Assemblies + false + + + portable + true + false + + + + + None + false + true + + + + + + + +``` + +### Создайте .pdb файлы: + +После сборки в Debug режиме в папке `Assemblies/` появится: +- `AIImages.dll` - ваш мод +- `AIImages.pdb` - отладочные символы ✨ + +--- + +## 🎯 Практические примеры + +### Пример 1: Отладка Harmony патча + +**PawnGizmoPatch.cs:** + +```csharp +[HarmonyPostfix] +public static IEnumerable Postfix(IEnumerable __result, Pawn __instance) +{ + Log.Message($"[AI Images] GetGizmos called for: {__instance.Name}"); // 👈 Лог + + foreach (Gizmo gizmo in __result) + { + yield return gizmo; + } + + if (__instance.IsColonist && __instance.Spawned && __instance.Faction == Faction.OfPlayer) + { + Log.Message($"[AI Images] Adding button for: {__instance.Name}"); // 👈 Лог + + yield return new Command_Action + { + defaultLabel = "AI Image", + defaultDesc = "Open AI Image window", + icon = ContentFinder.Get("UI/Commands/AttackMelee", true), + action = delegate() + { + Log.Message($"[AI Images] Button clicked!"); // 👈 Лог + Find.WindowStack.Add(new Window_AIImage(__instance)); + } + }; + } +} +``` + +### Пример 2: Отладка окна + +**Window_AIImage.cs:** + +```csharp +public override void DoWindowContents(Rect inRect) +{ + try + { + Log.Message($"[AI Images] Drawing window for: {pawn.Name}"); // 👈 Лог + + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(0f, 0f, inRect.width, 40f), "AI Image Window"); + + Text.Font = GameFont.Small; + Widgets.Label(new Rect(0f, 50f, inRect.width, 30f), "Pawn: " + pawn.NameShortColored.Resolve()); + + Widgets.DrawBoxSolid( + new Rect(10f, 100f, inRect.width - 20f, inRect.height - 150f), + new Color(0.2f, 0.2f, 0.2f, 0.5f) + ); + + Log.Message("[AI Images] Window drawn successfully"); // 👈 Лог + } + catch (System.Exception ex) + { + Log.Error($"[AI Images] Error drawing window: {ex}"); // 👈 Отлов ошибок + } +} +``` + +### Пример 3: Try-Catch для отлова ошибок + +```csharp +public static IEnumerable Postfix(IEnumerable __result, Pawn __instance) +{ + foreach (Gizmo gizmo in __result) + { + yield return gizmo; + } + + if (__instance.IsColonist && __instance.Spawned) + { + Command_Action button = null; + + try + { + button = new Command_Action + { + defaultLabel = "AI Image", + defaultDesc = "Open AI Image window", + icon = ContentFinder.Get("UI/Commands/AttackMelee", true), + action = delegate() + { + try + { + Find.WindowStack.Add(new Window_AIImage(__instance)); + } + catch (System.Exception ex) + { + Log.Error($"[AI Images] Failed to open window: {ex}"); + } + } + }; + } + catch (System.Exception ex) + { + Log.Error($"[AI Images] Failed to create button: {ex}"); + } + + if (button != null) + { + yield return button; + } + } +} +``` + +--- + +## 🚀 Быстрая отладка - Workflow + +### Вариант 1: С Breakpoints (Visual Studio/Rider) + +```bash +# 1. Сборка в Debug +cd Source/AIImages +dotnet build -c Debug + +# 2. Запустите RimWorld + +# 3. Attach отладчик (Ctrl+Alt+P в VS) + +# 4. Поставьте breakpoint в коде + +# 5. Триггерьте код в игре → останавливается на breakpoint +``` + +### Вариант 2: С логированием (быстрый) + +```bash +# 1. Добавьте Log.Message() в код + +# 2. Соберите +dotnet build + +# 3. Запустите RimWorld + +# 4. Ctrl+F12 → смотрите логи в реальном времени +``` + +--- + +## 📊 Полезные советы + +### 1. Используйте префикс в логах + +```csharp +Log.Message("[AI Images] Your message"); +``` + +Так проще искать в логе: `Ctrl+F` → `[AI Images]` + +### 2. Логируйте важные переменные + +```csharp +Log.Message($"[AI Images] Pawn: {pawn.Name}, Age: {pawn.ageTracker.AgeBiologicalYears}"); +``` + +### 3. Conditional compilation + +```csharp +#if DEBUG + Log.Message("[AI Images] Debug info"); +#endif +``` + +Эти строки будут работать только в Debug сборке! + +### 4. Проверяйте null + +```csharp +if (pawn?.health?.hediffSet != null) +{ + // Безопасно использовать +} +``` + +### 5. Используйте Debug Actions для тестирования + +Создавайте тестовые функции с `[DebugAction]` - это быстрее, чем играть вручную. + +--- + +## ⚠️ Частые проблемы + +### Breakpoint не срабатывает + +✅ Убедитесь, что: +- Проект собран в Debug (`dotnet build -c Debug`) +- Файл `.pdb` существует рядом с `.dll` +- Отладчик подключен к правильному процессу +- Код действительно выполняется (добавьте Log.Message для проверки) + +### "Symbols not loaded" + +✅ Решение: +- Удалите `Assemblies/*.dll` и `Assemblies/*.pdb` +- Пересоберите: `dotnet clean && dotnet build -c Debug` + +### Игра тормозит с отладчиком + +✅ Это нормально. Отладчик замедляет выполнение. + +--- + +## 🎓 Дополнительные материалы + +- **Visual Studio Debugging Guide**: https://docs.microsoft.com/en-us/visualstudio/debugger/ +- **Rider Debugging**: https://www.jetbrains.com/help/rider/Debugging_Code.html +- **dnSpy**: https://github.com/dnSpy/dnSpy + +--- + +## ✅ Готово! + +Теперь вы можете: +- ✅ Подключать отладчик к RimWorld +- ✅ Ставить breakpoints и смотреть переменные +- ✅ Использовать логирование +- ✅ Создавать Debug Actions для тестирования + +**Удачной отладки! 🐛→✨** + diff --git a/LoadFolders.xml b/LoadFolders.xml new file mode 100644 index 0000000..6e7f5e8 --- /dev/null +++ b/LoadFolders.xml @@ -0,0 +1,6 @@ + + + +
  • /
  • +
    +
    diff --git a/Source/AIImages/AIImages.csproj b/Source/AIImages/AIImages.csproj new file mode 100644 index 0000000..1a9f816 --- /dev/null +++ b/Source/AIImages/AIImages.csproj @@ -0,0 +1,22 @@ + + + net472 + latest + ..\..\Assemblies + false + + portable + true + false + + + + None + false + true + + + + + + diff --git a/Source/AIImages/AIImagesMod.cs b/Source/AIImages/AIImagesMod.cs new file mode 100644 index 0000000..4aac809 --- /dev/null +++ b/Source/AIImages/AIImagesMod.cs @@ -0,0 +1,19 @@ +using HarmonyLib; +using Verse; + +namespace AIImages +{ + /// + /// Main mod class that initializes Harmony patches + /// + [StaticConstructorOnStartup] + public static class AIImagesMod + { + static AIImagesMod() + { + var harmony = new Harmony("Mrleo1nid.aiimages"); + harmony.PatchAll(); + Log.Message("[AI Images] Mod initialized successfully"); + } + } +} diff --git a/Source/AIImages/PawnGizmoPatch.cs b/Source/AIImages/PawnGizmoPatch.cs new file mode 100644 index 0000000..f4e0850 --- /dev/null +++ b/Source/AIImages/PawnGizmoPatch.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using RimWorld; +using UnityEngine; +using Verse; + +#pragma warning disable IDE1006 // Naming Styles + +namespace AIImages +{ + /// + /// Harmony patch to add a gizmo (button) to all colonist pawns + /// + [HarmonyPatch(typeof(Pawn), "GetGizmos")] + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Style", + "IDE1006:Naming Styles", + Justification = "RimWorld Harmony patch naming convention" + )] + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Minor Code Smell", + "S101:Types should be named in PascalCase", + Justification = "RimWorld Harmony patch naming convention" + )] + public static class Pawn_GetGizmos_Patch + { + [HarmonyPostfix] + public static IEnumerable Postfix(IEnumerable __result, Pawn __instance) + { + // First, return all original gizmos + foreach (Gizmo gizmo in __result) + { + yield return gizmo; + } + + // Only add button to colonist pawns that are spawned + if ( + __instance.IsColonist + && __instance.Spawned + && __instance.Faction == Faction.OfPlayer + ) + { + yield return new Command_Action + { + defaultLabel = "AI Image", + defaultDesc = "Open AI Image window", + icon = ContentFinder.Get("UI/Commands/AttackMelee", true), + action = delegate() + { + // Проверяем, открыто ли уже окно AI Image + Window_AIImage existingWindow = Find + .WindowStack.Windows.OfType() + .FirstOrDefault(); + + if (existingWindow != null) + { + // Если окно открыто, обновляем пешку + existingWindow.UpdatePawn(__instance); + } + else + { + // Если окна нет, создаём новое + Find.WindowStack.Add(new Window_AIImage(__instance)); + } + }, + }; + } + } + } +} diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs new file mode 100644 index 0000000..a309eaa --- /dev/null +++ b/Source/AIImages/Window_AIImage.cs @@ -0,0 +1,289 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using RimWorld; +using UnityEngine; +using Verse; + +#pragma warning disable IDE1006 // Naming Styles + +namespace AIImages +{ + /// + /// Empty window that opens when clicking the pawn button + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Style", + "IDE1006:Naming Styles", + Justification = "RimWorld Window naming convention" + )] + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Minor Code Smell", + "S101:Types should be named in PascalCase", + Justification = "RimWorld Window naming convention" + )] + public class Window_AIImage : Window + { + private Pawn pawn; + + public Window_AIImage(Pawn pawn) + { + this.pawn = pawn; + this.doCloseX = true; + this.doCloseButton = true; + this.forcePause = false; // Не ставим игру на паузу + this.absorbInputAroundWindow = false; // Не блокируем клики вне окна + this.draggable = true; // Делаем окно перемещаемым + this.preventCameraMotion = false; // Не блокируем управление камерой + } + + public override Vector2 InitialSize => new Vector2(700f, 600f); + + private Vector2 scrollPosition = Vector2.zero; + + /// + /// Обновляет текущую пешку в окне + /// + public void UpdatePawn(Pawn newPawn) + { + this.pawn = newPawn; + } + + /// + /// Получить текущую пешку + /// + public Pawn CurrentPawn => pawn; + + /// + /// Вызывается каждый кадр для обновления окна + /// + public override void WindowUpdate() + { + base.WindowUpdate(); + + // Проверяем, изменилась ли выбранная пешка + Pawn selectedPawn = Find.Selector.SelectedPawns.FirstOrDefault(p => + p.IsColonist && p.Spawned && p.Faction == Faction.OfPlayer + ); + + // Если выбрана новая колонистская пешка, обновляем окно + if (selectedPawn != null && selectedPawn != pawn) + { + pawn = selectedPawn; + } + } + + /// + /// Получает описание внешности персонажа + /// + private string GetAppearanceDescription() + { + if (pawn?.story == null) + return "Информация о внешности недоступна"; + + StringBuilder sb = new StringBuilder(); + + // Пол + sb.AppendLine($"Пол: {pawn.gender.GetLabel()}"); + + // Возраст + sb.AppendLine($"Возраст: {pawn.ageTracker.AgeBiologicalYears} лет"); + + // Тип тела + if (pawn.story.bodyType != null) + { + sb.AppendLine($"Тип тела: {pawn.story.bodyType.defName}"); + } + + // Цвет кожи + if (pawn.story.SkinColor != null) + { + Color skinColor = pawn.story.SkinColor; + sb.AppendLine( + $"Цвет кожи: RGB({skinColor.r:F2}, {skinColor.g:F2}, {skinColor.b:F2})" + ); + } + + // Волосы + if (pawn.story.hairDef != null) + { + sb.AppendLine($"Прическа: {pawn.story.hairDef.label}"); + if (pawn.story.HairColor != null) + { + sb.AppendLine( + $"Цвет волос: RGB({pawn.story.HairColor.r:F2}, {pawn.story.HairColor.g:F2}, {pawn.story.HairColor.b:F2})" + ); + } + } + + // Черты характера + if (pawn.story.traits?.allTraits != null && pawn.story.traits.allTraits.Any()) + { + sb.AppendLine("\nЧерты характера:"); + foreach (var trait in pawn.story.traits.allTraits) + { + sb.AppendLine($" • {trait.LabelCap}"); + } + } + + return sb.ToString(); + } + + /// + /// Получает описание одежды персонажа + /// + private string GetApparelDescription() + { + if (pawn?.apparel == null) + return "Информация об одежде недоступна"; + + StringBuilder sb = new StringBuilder(); + List wornApparel = pawn.apparel.WornApparel; + + if (wornApparel == null || !wornApparel.Any()) + { + sb.AppendLine("Персонаж ничего не носит"); + } + else + { + sb.AppendLine($"Одежда ({wornApparel.Count} предметов):\n"); + foreach (Apparel apparel in wornApparel) + { + FormatApparelItem(sb, apparel); + } + } + + return sb.ToString(); + } + + /// + /// Форматирует информацию об одном предмете одежды + /// + private void FormatApparelItem(StringBuilder sb, Apparel apparel) + { + sb.AppendLine($"• {apparel.LabelCap}"); + + if (apparel.TryGetQuality(out QualityCategory quality)) + { + sb.AppendLine($" Качество: {quality.GetLabel()}"); + } + + if (apparel.Stuff != null) + { + sb.AppendLine($" Материал: {apparel.Stuff.LabelCap}"); + } + + if (apparel.HitPoints < apparel.MaxHitPoints) + { + int percentage = (int)((float)apparel.HitPoints / apparel.MaxHitPoints * 100); + sb.AppendLine( + $" Прочность: {apparel.HitPoints}/{apparel.MaxHitPoints} ({percentage}%)" + ); + } + + if (apparel.DrawColor != Color.white) + { + sb.AppendLine( + $" Цвет: RGB({apparel.DrawColor.r:F2}, {apparel.DrawColor.g:F2}, {apparel.DrawColor.b:F2})" + ); + } + + sb.AppendLine(); + } + + public override void DoWindowContents(Rect inRect) + { + float curY = 0f; + + // Заголовок + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(0f, curY, inRect.width, 40f), "AI Image Window"); + curY += 45f; + + // Имя пешки + Text.Font = GameFont.Small; + Widgets.Label( + new Rect(0f, curY, inRect.width, 30f), + "Персонаж: " + pawn.NameShortColored.Resolve() + ); + curY += 40f; + + // Разделитель + Widgets.DrawLineHorizontal(0f, curY, inRect.width); + curY += 10f; + + // Область для прокрутки контента + Rect scrollRect = new Rect(0f, curY, inRect.width, inRect.height - curY); + Rect scrollViewRect = new Rect( + 0f, + 0f, + scrollRect.width - 20f, + CalculateContentHeight() + ); + + Widgets.BeginScrollView(scrollRect, ref scrollPosition, scrollViewRect); + + float contentY = 0f; + + // Секция "Внешность" + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(10f, contentY, scrollViewRect.width - 20f, 30f), "Внешность"); + contentY += 35f; + + Text.Font = GameFont.Small; + string appearanceText = GetAppearanceDescription(); + float appearanceHeight = Text.CalcHeight(appearanceText, scrollViewRect.width - 30f); + Widgets.Label( + new Rect(20f, contentY, scrollViewRect.width - 30f, appearanceHeight), + appearanceText + ); + contentY += appearanceHeight + 20f; + + // Разделитель + Widgets.DrawLineHorizontal(10f, contentY, scrollViewRect.width - 20f); + contentY += 15f; + + // Секция "Одежда" + Text.Font = GameFont.Medium; + Widgets.Label(new Rect(10f, contentY, scrollViewRect.width - 20f, 30f), "Одежда"); + contentY += 35f; + + Text.Font = GameFont.Small; + string apparelText = GetApparelDescription(); + float apparelHeight = Text.CalcHeight(apparelText, scrollViewRect.width - 30f); + Widgets.Label( + new Rect(20f, contentY, scrollViewRect.width - 30f, apparelHeight), + apparelText + ); + + Widgets.EndScrollView(); + } + + /// + /// Вычисляет высоту всего контента для прокрутки + /// + private float CalculateContentHeight() + { + float height = 0f; + + // Заголовок "Внешность" + height += 35f; + + // Текст внешности + string appearanceText = GetAppearanceDescription(); + height += Text.CalcHeight(appearanceText, 640f) + 20f; + + // Разделитель + height += 15f; + + // Заголовок "Одежда" + height += 35f; + + // Текст одежды + string apparelText = GetApparelDescription(); + height += Text.CalcHeight(apparelText, 640f) + 20f; + + return height; + } + } +}