317 lines
11 KiB
C#
317 lines
11 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using RimWorld;
|
||
using UnityEngine;
|
||
using Verse;
|
||
|
||
#pragma warning disable IDE1006 // Naming Styles
|
||
|
||
namespace AIImages
|
||
{
|
||
/// <summary>
|
||
/// Empty window that opens when clicking the pawn button
|
||
/// </summary>
|
||
[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;
|
||
|
||
/// <summary>
|
||
/// Обновляет текущую пешку в окне
|
||
/// </summary>
|
||
public void UpdatePawn(Pawn newPawn)
|
||
{
|
||
this.pawn = newPawn;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получить текущую пешку
|
||
/// </summary>
|
||
public Pawn CurrentPawn => pawn;
|
||
|
||
/// <summary>
|
||
/// Вызывается каждый кадр для обновления окна
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает описание внешности персонажа
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает описание одежды персонажа
|
||
/// </summary>
|
||
private string GetApparelDescription()
|
||
{
|
||
if (pawn?.apparel == null)
|
||
return "AIImages.Apparel.NoInfo".Translate();
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
List<Apparel> 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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Форматирует информацию об одном предмете одежды
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
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
|
||
);
|
||
|
||
Widgets.EndScrollView();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Вычисляет высоту всего контента для прокрутки
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|