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, 700f);
private Vector2 scrollPosition = Vector2.zero;
private float copiedMessageTime = 0f;
///
/// Обновляет текущую пешку в окне
///
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;
}
// Уменьшаем таймер сообщения о копировании
if (copiedMessageTime > 0f)
{
copiedMessageTime -= Time.deltaTime;
}
}
///
/// Получает описание внешности персонажа
///
private string GetAppearanceDescription()
{
if (pawn?.story == null)
return "AIImages.Appearance.NoInfo".Translate();
StringBuilder sb = new StringBuilder();
// Пол
sb.AppendLine("AIImages.Appearance.Gender".Translate(pawn.gender.GetLabel()));
// Возраст
sb.AppendLine("AIImages.Appearance.Age".Translate(pawn.ageTracker.AgeBiologicalYears));
// Тип тела
if (pawn.story.bodyType != null)
{
sb.AppendLine(
"AIImages.Appearance.BodyType".Translate(pawn.story.bodyType.defName)
);
}
// Цвет кожи
if (pawn.story.SkinColor != null)
{
Color skinColor = pawn.story.SkinColor;
sb.AppendLine(
"AIImages.Appearance.SkinColor".Translate(
skinColor.r.ToString("F2"),
skinColor.g.ToString("F2"),
skinColor.b.ToString("F2")
)
);
}
// Волосы
if (pawn.story.hairDef != null)
{
sb.AppendLine("AIImages.Appearance.Hairstyle".Translate(pawn.story.hairDef.label));
if (pawn.story.HairColor != null)
{
sb.AppendLine(
"AIImages.Appearance.HairColor".Translate(
pawn.story.HairColor.r.ToString("F2"),
pawn.story.HairColor.g.ToString("F2"),
pawn.story.HairColor.b.ToString("F2")
)
);
}
}
// Черты характера
if (pawn.story.traits?.allTraits != null && pawn.story.traits.allTraits.Any())
{
sb.AppendLine("\n" + "AIImages.Appearance.Traits".Translate());
foreach (var trait in pawn.story.traits.allTraits)
{
sb.AppendLine($" • {trait.LabelCap}");
}
}
return sb.ToString();
}
///
/// Получает описание одежды персонажа
///
private string GetApparelDescription()
{
if (pawn?.apparel == null)
return "AIImages.Apparel.NoInfo".Translate();
StringBuilder sb = new StringBuilder();
List wornApparel = pawn.apparel.WornApparel;
if (wornApparel == null || !wornApparel.Any())
{
sb.AppendLine("AIImages.Apparel.NoClothes".Translate());
}
else
{
sb.AppendLine("AIImages.Apparel.ListHeader".Translate(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("AIImages.Apparel.Quality".Translate(quality.GetLabel()));
}
if (apparel.Stuff != null)
{
sb.AppendLine("AIImages.Apparel.Material".Translate(apparel.Stuff.LabelCap));
}
if (apparel.HitPoints < apparel.MaxHitPoints)
{
int percentage = (int)((float)apparel.HitPoints / apparel.MaxHitPoints * 100);
sb.AppendLine(
"AIImages.Apparel.Durability".Translate(
apparel.HitPoints,
apparel.MaxHitPoints,
percentage
)
);
}
if (apparel.DrawColor != Color.white)
{
sb.AppendLine(
"AIImages.Apparel.Color".Translate(
apparel.DrawColor.r.ToString("F2"),
apparel.DrawColor.g.ToString("F2"),
apparel.DrawColor.b.ToString("F2")
)
);
}
sb.AppendLine();
}
///
/// Генерирует промпт для Stable Diffusion на основе внешности персонажа
///
private string GenerateStableDiffusionPrompt()
{
if (pawn?.story == null)
return "portrait of a person";
StringBuilder prompt = new StringBuilder("portrait of a ");
prompt.Append(GetAgeAndGenderDescription());
prompt.Append(GetBodyTypeDescription());
prompt.Append(GetSkinToneDescription());
prompt.Append(GetHairDescription());
prompt.Append(GetApparelPromptDescription());
prompt.Append(
"realistic, detailed, high quality, professional lighting, 8k, photorealistic"
);
return prompt.ToString();
}
private string GetAgeAndGenderDescription()
{
string ageGroup = pawn.ageTracker.AgeBiologicalYears switch
{
< 18 => "young",
< 30 => "young adult",
< 50 => "middle-aged",
_ => "mature",
};
return $"{ageGroup} {pawn.gender.GetLabel()}, ";
}
private string GetBodyTypeDescription()
{
if (pawn.story.bodyType == null)
return "";
string bodyDesc = pawn.story.bodyType.defName.ToLower() switch
{
"thin" => "slender build",
"hulk" => "muscular build",
"fat" => "heavyset build",
_ => "average build",
};
return $"{bodyDesc}, ";
}
private string GetSkinToneDescription()
{
if (pawn.story.SkinColor == null)
return "";
float brightness =
(pawn.story.SkinColor.r + pawn.story.SkinColor.g + pawn.story.SkinColor.b) / 3f;
string skinTone = brightness switch
{
>= 0.8f => "fair skin",
>= 0.6f => "light skin",
>= 0.4f => "olive skin",
>= 0.2f => "brown skin",
_ => "dark skin",
};
return $"{skinTone}, ";
}
private string GetHairDescription()
{
if (pawn.story.hairDef == null)
return "";
string result = $"{pawn.story.hairDef.label.ToLower()} hair";
if (pawn.story.HairColor != null)
{
result += $", {GetColorDescription(pawn.story.HairColor)} hair color";
}
return result + ", ";
}
private string GetApparelPromptDescription()
{
if (pawn.apparel?.WornApparel == null || !pawn.apparel.WornApparel.Any())
return "";
List items = pawn
.apparel.WornApparel.Take(3)
.Select(a =>
a.Stuff != null
? $"{a.Stuff.label.ToLower()} {a.def.label.ToLower()}"
: a.def.label.ToLower()
)
.ToList();
return $"wearing {string.Join(", ", items)}, ";
}
///
/// Получает текстовое описание цвета
///
private string GetColorDescription(Color color)
{
// Определяем доминирующий цвет
float max = Mathf.Max(color.r, color.g, color.b);
float min = Mathf.Min(color.r, color.g, color.b);
float diff = max - min;
if (diff < 0.1f)
{
// Оттенки серого
return max switch
{
> 0.8f => "white",
> 0.6f => "light gray",
> 0.4f => "gray",
> 0.2f => "dark gray",
_ => "black",
};
}
// Цветные
const float epsilon = 0.001f;
if (Mathf.Abs(color.r - max) < epsilon)
{
return color.g > color.b ? "orange" : "red";
}
else if (Mathf.Abs(color.g - max) < epsilon)
{
return color.r > color.b ? "yellow" : "green";
}
else
{
return color.r > color.g ? "purple" : "blue";
}
}
public override void DoWindowContents(Rect inRect)
{
float curY = 0f;
// Заголовок
Text.Font = GameFont.Medium;
Widgets.Label(
new Rect(0f, curY, inRect.width, 40f),
"AIImages.Window.Title".Translate()
);
curY += 45f;
// Имя пешки
Text.Font = GameFont.Small;
Widgets.Label(
new Rect(0f, curY, inRect.width, 30f),
"AIImages.Window.PawnLabel".Translate(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),
"AIImages.Appearance.SectionTitle".Translate()
);
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),
"AIImages.Apparel.SectionTitle".Translate()
);
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
);
contentY += apparelHeight + 20f;
// Разделитель
Widgets.DrawLineHorizontal(10f, contentY, scrollViewRect.width - 20f);
contentY += 15f;
// Секция "Stable Diffusion Промпт"
Text.Font = GameFont.Medium;
Widgets.Label(
new Rect(10f, contentY, scrollViewRect.width - 20f, 30f),
"AIImages.Prompt.SectionTitle".Translate()
);
contentY += 35f;
// Промпт текст
Text.Font = GameFont.Small;
string promptText = GenerateStableDiffusionPrompt();
float promptHeight = Text.CalcHeight(promptText, scrollViewRect.width - 30f);
Widgets.Label(
new Rect(20f, contentY, scrollViewRect.width - 30f, promptHeight),
promptText
);
contentY += promptHeight + 10f;
// Кнопка копирования
Rect copyButtonRect = new Rect(20f, contentY, 150f, 30f);
if (Widgets.ButtonText(copyButtonRect, "AIImages.Prompt.CopyButton".Translate()))
{
GUIUtility.systemCopyBuffer = promptText;
copiedMessageTime = 2f; // Показываем сообщение на 2 секунды
}
// Сообщение о копировании
if (copiedMessageTime > 0f)
{
Rect copiedRect = new Rect(copyButtonRect.xMax + 10f, contentY, 100f, 30f);
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); // Затухающий зеленый
Widgets.Label(copiedRect, "AIImages.Prompt.Copied".Translate());
GUI.color = Color.white;
}
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;
// Разделитель
height += 15f;
// Заголовок "Промпт"
height += 35f;
// Текст промпта
string promptText = GenerateStableDiffusionPrompt();
height += Text.CalcHeight(promptText, 640f) + 10f;
// Кнопка и отступ
height += 30f + 20f;
return height;
}
}
}