diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll
index 0db4f3d..78edded 100644
Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ
diff --git a/Languages/English/Keyed/AIImages.xml b/Languages/English/Keyed/AIImages.xml
index 3f4b609..6b94cf2 100644
--- a/Languages/English/Keyed/AIImages.xml
+++ b/Languages/English/Keyed/AIImages.xml
@@ -118,4 +118,7 @@
Successfully deleted {0} images
Open gallery
({0})
+
+ Log Events
+ Generate AI Image
diff --git a/Languages/Russian/Keyed/AIImages.xml b/Languages/Russian/Keyed/AIImages.xml
index 0cd8ad0..15d0079 100644
--- a/Languages/Russian/Keyed/AIImages.xml
+++ b/Languages/Russian/Keyed/AIImages.xml
@@ -118,4 +118,7 @@
Успешно удалено {0} изображений
Открыть галерею
({0})
+
+ События из журнала
+ Сгенерировать AI изображение
diff --git a/Source/AIImages/Models/PawnAppearanceData.cs b/Source/AIImages/Models/PawnAppearanceData.cs
index 4b66f39..a01d579 100644
--- a/Source/AIImages/Models/PawnAppearanceData.cs
+++ b/Source/AIImages/Models/PawnAppearanceData.cs
@@ -22,6 +22,7 @@ namespace AIImages.Models
public Color HairColor { get; set; }
public List Traits { get; set; }
public List Apparel { get; set; }
+ public string EventDescription { get; set; }
public PawnAppearanceData()
{
diff --git a/Source/AIImages/Patches/PawnLogPatch.cs b/Source/AIImages/Patches/PawnLogPatch.cs
new file mode 100644
index 0000000..73833ff
--- /dev/null
+++ b/Source/AIImages/Patches/PawnLogPatch.cs
@@ -0,0 +1,241 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+using RimWorld;
+using UnityEngine;
+using Verse;
+
+namespace AIImages.Patches
+{
+ ///
+ /// Патч для получения записей лога пешки через ITab_Pawn_Log
+ ///
+ [HarmonyPatch(typeof(ITab_Pawn_Log), "FillTab")]
+ public static class ITab_Pawn_Log_Patch
+ {
+ private static Pawn lastPawn = null;
+ private static List cachedEntries = new List();
+
+ [HarmonyPrefix]
+ public static void Prefix(ITab_Pawn_Log __instance)
+ {
+ try
+ {
+ // Получаем пешку через рефлексию
+ var selPawnProp = AccessTools.Property(typeof(ITab), "SelPawn");
+ if (selPawnProp != null)
+ {
+ var pawn = selPawnProp.GetValue(__instance) as Pawn;
+ if (pawn != null)
+ {
+ // Пробуем получить записи напрямую через Pawn.logs
+ var logsProperty = AccessTools.Property(typeof(Pawn), "logs");
+ if (logsProperty != null)
+ {
+ var logs = logsProperty.GetValue(pawn);
+ if (logs != null)
+ {
+ // Пробуем AllEntries
+ var allEntriesProperty = AccessTools.Property(
+ logs.GetType(),
+ "AllEntries"
+ );
+ if (allEntriesProperty != null)
+ {
+ var entries = allEntriesProperty.GetValue(logs);
+ if (entries is IEnumerable entriesList)
+ {
+ cachedEntries = entriesList.ToList();
+ lastPawn = pawn;
+ UnityEngine.Debug.Log(
+ $"[AI Images] Cached {cachedEntries.Count} log entries for {pawn.Name}"
+ );
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ UnityEngine.Debug.LogWarning(
+ $"[AI Images] ITab_Pawn_Log patch error: {ex.Message}"
+ );
+ }
+ }
+
+ public static List GetCachedEntries(Pawn pawn)
+ {
+ if (pawn == lastPawn && cachedEntries != null && cachedEntries.Any())
+ {
+ return cachedEntries;
+ }
+
+ // Если кэш не подходит, пытаемся получить напрямую
+ if (pawn != null)
+ {
+ try
+ {
+ var logsProperty = AccessTools.Property(typeof(Pawn), "logs");
+ if (logsProperty != null)
+ {
+ var logs = logsProperty.GetValue(pawn);
+ if (logs != null)
+ {
+ var allEntriesProperty = AccessTools.Property(
+ logs.GetType(),
+ "AllEntries"
+ );
+ if (allEntriesProperty != null)
+ {
+ var entries = allEntriesProperty.GetValue(logs);
+ if (entries is IEnumerable entriesList)
+ {
+ var list = entriesList.ToList();
+ if (list.Any())
+ {
+ cachedEntries = list;
+ lastPawn = pawn;
+ return list;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ UnityEngine.Debug.LogWarning(
+ $"[AI Images] Error getting entries directly: {ex.Message}"
+ );
+ }
+ }
+
+ return new List();
+ }
+ }
+
+ ///
+ /// Патч для получения записей лога пешки
+ /// Упрощённая версия - только для получения данных
+ ///
+ public static class PawnLogPatch
+ {
+ ///
+ /// Получает все записи лога пешки
+ ///
+ public static IEnumerable GetAllLogEntries(Pawn pawn)
+ {
+ // Сначала пробуем получить из кэша ITab_Pawn_Log
+ var cachedEntries = ITab_Pawn_Log_Patch.GetCachedEntries(pawn);
+ if (cachedEntries != null && cachedEntries.Any())
+ {
+ return cachedEntries;
+ }
+
+ // Затем пробуем другие способы
+ return GetAllLogEntriesInternal(pawn);
+ }
+
+ ///
+ /// Внутренний метод получения записей лога
+ ///
+ private static IEnumerable GetAllLogEntriesInternal(Pawn pawn)
+ {
+ if (pawn == null)
+ return Enumerable.Empty();
+
+ try
+ {
+ // Способ 1: Через property через reflection
+ var logsProperty = AccessTools.Property(typeof(Pawn), "logs");
+ if (logsProperty != null)
+ {
+ var logs = logsProperty.GetValue(pawn);
+ if (logs != null)
+ {
+ var allEntriesProperty = AccessTools.Property(logs.GetType(), "AllEntries");
+ if (allEntriesProperty != null)
+ {
+ var entries = allEntriesProperty.GetValue(logs);
+ if (entries is IEnumerable entriesList)
+ {
+ return entriesList;
+ }
+ }
+
+ // Пробуем метод GetEntries
+ var getEntriesMethod = AccessTools.Method(logs.GetType(), "GetEntries");
+ if (getEntriesMethod != null)
+ {
+ var entries = getEntriesMethod.Invoke(logs, null);
+ if (entries is IEnumerable entriesList)
+ {
+ return entriesList;
+ }
+ }
+
+ // Пробуем поле entries
+ var entriesField = AccessTools.Field(logs.GetType(), "entries");
+ if (entriesField != null)
+ {
+ var entries = entriesField.GetValue(logs);
+ if (entries is IEnumerable entriesList)
+ {
+ return entriesList;
+ }
+ if (entries is System.Collections.IList entriesCollection)
+ {
+ return entriesCollection.Cast();
+ }
+ }
+ }
+ }
+
+ // Способ 3: Через story.logs
+ if (pawn.story != null)
+ {
+ var logsField = AccessTools.Field(pawn.story.GetType(), "logs");
+ if (logsField != null)
+ {
+ var logs = logsField.GetValue(pawn.story);
+ if (logs != null)
+ {
+ var allEntriesProperty = AccessTools.Property(
+ logs.GetType(),
+ "AllEntries"
+ );
+ if (allEntriesProperty != null)
+ {
+ var entries = allEntriesProperty.GetValue(logs);
+ if (entries is IEnumerable entriesList)
+ {
+ return entriesList;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ // Логируем ошибку для отладки
+ UnityEngine.Debug.LogWarning(
+ $"[AI Images] Error getting log entries for {pawn?.Name}: {ex.Message}"
+ );
+ }
+
+ // Если все способы не сработали, логируем информацию
+ UnityEngine.Debug.Log(
+ $"[AI Images] Could not get log entries for {pawn?.Name}. "
+ + $"logs property exists: {AccessTools.Property(typeof(Pawn), "logs") != null}, "
+ + $"story property exists: {AccessTools.Property(typeof(Pawn), "story") != null}"
+ );
+
+ return Enumerable.Empty();
+ }
+ }
+}
diff --git a/Source/AIImages/Services/AdvancedPromptGenerator.cs b/Source/AIImages/Services/AdvancedPromptGenerator.cs
index 7f334be..2e5ecd8 100644
--- a/Source/AIImages/Services/AdvancedPromptGenerator.cs
+++ b/Source/AIImages/Services/AdvancedPromptGenerator.cs
@@ -119,6 +119,33 @@ namespace AIImages.Services
return prompt.ToString().Trim().TrimEnd(',');
}
+ ///
+ /// Генерирует позитивный промпт на основе данных о персонаже и события
+ ///
+ public string GeneratePositivePromptWithEvent(
+ PawnAppearanceData appearanceData,
+ StableDiffusionSettings settings,
+ string eventDescription
+ )
+ {
+ if (appearanceData == null)
+ return "portrait of a person";
+
+ // Генерируем базовый промпт
+ string basePrompt = GeneratePositivePrompt(appearanceData, settings);
+
+ // Добавляем описание события, если оно есть
+ if (!string.IsNullOrEmpty(eventDescription))
+ {
+ StringBuilder prompt = new StringBuilder(basePrompt);
+ prompt.Append(", ");
+ prompt.Append(eventDescription.ToLower());
+ return prompt.ToString();
+ }
+
+ return basePrompt;
+ }
+
public string GenerateNegativePrompt(StableDiffusionSettings settings)
{
StringBuilder negativePrompt = new StringBuilder();
diff --git a/Source/AIImages/Services/IPromptGeneratorService.cs b/Source/AIImages/Services/IPromptGeneratorService.cs
index 34e4301..48c1f0d 100644
--- a/Source/AIImages/Services/IPromptGeneratorService.cs
+++ b/Source/AIImages/Services/IPromptGeneratorService.cs
@@ -15,6 +15,15 @@ namespace AIImages.Services
StableDiffusionSettings settings
);
+ ///
+ /// Генерирует позитивный промпт на основе данных о персонаже и события
+ ///
+ string GeneratePositivePromptWithEvent(
+ PawnAppearanceData appearanceData,
+ StableDiffusionSettings settings,
+ string eventDescription
+ );
+
///
/// Генерирует негативный промпт на основе настроек
///
diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs
index 384aaed..9f5fe07 100644
--- a/Source/AIImages/Window_AIImage.cs
+++ b/Source/AIImages/Window_AIImage.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using AIImages.Helpers;
using AIImages.Models;
+using AIImages.Patches;
using AIImages.Services;
using RimWorld;
using UnityEngine;
@@ -72,11 +73,13 @@ namespace AIImages
private Vector2 mainScrollPosition = Vector2.zero;
private Vector2 promptScrollPosition = Vector2.zero;
private Vector2 negativePromptScrollPosition = Vector2.zero;
+ private Vector2 logEntriesScrollPosition = Vector2.zero;
private float copiedMessageTime = 0f;
// Состояние сворачиваемых секций промптов
private bool showPositivePrompt = false;
private bool showNegativePrompt = false;
+ private bool showLogEntries = true; // Развёрнута по умолчанию
///
/// Обновляет данные персонажа
@@ -190,6 +193,24 @@ namespace AIImages
///
public Pawn CurrentPawn => pawn;
+ ///
+ /// Генерирует изображение для пешки с описанием события
+ ///
+ public void GenerateImageForEvent(string eventDescription)
+ {
+ if (pawn == null || isGenerating)
+ return;
+
+ // Устанавливаем описание события
+ if (appearanceData != null)
+ {
+ appearanceData.EventDescription = eventDescription;
+ }
+
+ // Запускаем генерацию
+ StartGeneration();
+ }
+
///
/// Отладочный метод для проверки состояния всех пешек
///
@@ -307,11 +328,24 @@ namespace AIImages
try
{
- // Генерируем промпты
- string positivePrompt = promptGeneratorService.GeneratePositivePrompt(
- appearanceData,
- generationSettings
- );
+ // Генерируем промпты - если есть описание события, используем его
+ string positivePrompt;
+ if (!string.IsNullOrEmpty(appearanceData?.EventDescription))
+ {
+ positivePrompt = promptGeneratorService.GeneratePositivePromptWithEvent(
+ appearanceData,
+ generationSettings,
+ appearanceData.EventDescription
+ );
+ }
+ else
+ {
+ positivePrompt = promptGeneratorService.GeneratePositivePrompt(
+ appearanceData,
+ generationSettings
+ );
+ }
+
string negativePrompt = promptGeneratorService.GenerateNegativePrompt(
generationSettings
);
@@ -575,7 +609,8 @@ namespace AIImages
contentY = DrawTraits(parentRect, contentY, lineHeight);
contentY = DrawGenes(parentRect, contentY, lineHeight);
contentY = DrawHediffs(parentRect, contentY, lineHeight);
- DrawApparel(parentRect, contentY, lineHeight);
+ contentY = DrawApparel(parentRect, contentY, lineHeight);
+ contentY = DrawLogEntries(parentRect, contentY, lineHeight);
}
///
@@ -757,13 +792,13 @@ namespace AIImages
///
/// Отрисовывает одежду персонажа
///
- private void DrawApparel(Rect parentRect, float startY, float lineHeight)
+ private float DrawApparel(Rect parentRect, float startY, float lineHeight)
{
float contentY = startY;
var apparel = pawn.apparel?.WornApparel;
if (apparel == null || !apparel.Any())
- return;
+ return contentY;
contentY += 15f;
Text.Font = GameFont.Small;
@@ -785,6 +820,141 @@ namespace AIImages
);
contentY += apparelHeight;
}
+
+ return contentY;
+ }
+
+ ///
+ /// Отрисовывает список записей лога пешки
+ ///
+ private float DrawLogEntries(Rect parentRect, float startY, float lineHeight)
+ {
+ float contentY = startY;
+
+ // Получаем записи лога
+ var entries = PawnLogPatch.GetAllLogEntries(pawn).ToList();
+ int entriesCount = entries?.Count ?? 0;
+
+ // Отладочная информация (временно)
+ if (entriesCount == 0 && pawn != null)
+ {
+ // Пробуем проверить, есть ли вообще доступ к логам
+ try
+ {
+ var logsProp = HarmonyLib.AccessTools.Property(typeof(Pawn), "logs");
+ var hasLogs = logsProp != null;
+
+ // Выводим в консоль для отладки
+ DebugLogger.Log(
+ $"[AI Images] Log entries count: {entriesCount}, Has logs property: {hasLogs}, Pawn: {pawn?.Name}"
+ );
+ }
+ catch (System.Exception ex)
+ {
+ DebugLogger.Warning($"[AI Images] Debug error: {ex.Message}");
+ }
+ }
+
+ contentY += 15f;
+
+ // Заголовок секции - всегда показываем, даже если записей нет
+ Text.Font = GameFont.Small;
+ Rect headerRect = new Rect(
+ parentRect.x + 5f,
+ contentY,
+ parentRect.width - 10f,
+ lineHeight + 5f
+ );
+
+ // Кнопка сворачивания/разворачивания
+ string headerText = "AIImages.Log.Entries".Translate() + $" ({entriesCount})";
+ if (Widgets.ButtonText(headerRect, headerText))
+ {
+ showLogEntries = !showLogEntries;
+ }
+ contentY += lineHeight + 10f;
+
+ // Разделитель
+ Widgets.DrawLineHorizontal(parentRect.x, contentY, parentRect.width);
+ contentY += 10f;
+
+ // Список записей (если развёрнуто и есть записи)
+ if (showLogEntries && entries != null && entries.Any())
+ {
+ float entryWidth = parentRect.width - 25f;
+ float scrollViewHeight = Mathf.Min(entries.Count * 30f + 20f, 300f);
+
+ Rect scrollViewRect = new Rect(
+ parentRect.x + 5f,
+ contentY,
+ entryWidth + 15f,
+ scrollViewHeight
+ );
+
+ Rect viewRect = new Rect(0f, 0f, entryWidth, entries.Count * 30f);
+
+ Widgets.BeginScrollView(scrollViewRect, ref logEntriesScrollPosition, viewRect);
+
+ Text.Font = GameFont.Tiny;
+ float entryY = 0f;
+ const float entryHeight = 28f;
+ const float buttonSize = 20f;
+
+ foreach (var entry in entries.Take(50)) // Ограничиваем для производительности
+ {
+ string entryText = entry.ToGameStringFromPOV(pawn);
+ if (string.IsNullOrEmpty(entryText))
+ continue;
+
+ // Обрезаем длинный текст
+ string shortText =
+ entryText.Length > 60 ? entryText.Substring(0, 57) + "..." : entryText;
+
+ Rect entryRect = new Rect(
+ 0f,
+ entryY,
+ entryWidth - buttonSize - 5f,
+ entryHeight
+ );
+ Rect buttonRect = new Rect(
+ entryWidth - buttonSize,
+ entryY + (entryHeight - buttonSize) / 2f,
+ buttonSize,
+ buttonSize
+ );
+
+ // Фон записи
+ Widgets.DrawBoxSolid(entryRect, new Color(0.15f, 0.15f, 0.15f, 0.3f));
+
+ // Текст записи
+ Widgets.Label(entryRect.ContractedBy(3f), shortText);
+
+ // Кнопка генерации изображения
+ Texture2D iconTexture = ContentFinder.Get(
+ "UI/Commands/AIImage",
+ true
+ );
+ if (iconTexture != null)
+ {
+ if (Widgets.ButtonImage(buttonRect, iconTexture))
+ {
+ GenerateImageForEvent(entryText);
+ }
+
+ TooltipHandler.TipRegion(
+ buttonRect,
+ "AIImages.Log.GenerateImage".Translate()
+ );
+ }
+
+ entryY += entryHeight;
+ }
+
+ Widgets.EndScrollView();
+ contentY += scrollViewHeight + 10f;
+ }
+
+ return contentY;
}
private void DrawRightColumn(Rect rect)
@@ -1311,7 +1481,12 @@ namespace AIImages
galleryLabel += " " + "AIImages.Gallery.ImagesCount".Translate(imageCount);
}
- if (Widgets.ButtonText(new Rect(galleryButtonX, curY, galleryButtonWidth, 35f), galleryLabel))
+ if (
+ Widgets.ButtonText(
+ new Rect(galleryButtonX, curY, galleryButtonWidth, 35f),
+ galleryLabel
+ )
+ )
{
var galleryWindow = new Window_AIGallery(pawn);
Find.WindowStack.Add(galleryWindow);