Добавьте файлы проекта.

This commit is contained in:
Leonid Pershin
2025-10-26 17:02:04 +03:00
parent 842a1a2c70
commit caa830ba06
12 changed files with 1136 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>latest</LangVersion>
<OutputPath>..\..\Assemblies</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- Debug configuration - with symbols for debugging -->
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
</PropertyGroup>
<!-- Release configuration - no debug symbols -->
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugType>None</DebugType>
<DebugSymbols>false</DebugSymbols>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.4566" />
<PackageReference Include="Lib.Harmony" Version="2.4.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
using HarmonyLib;
using Verse;
namespace AIImages
{
/// <summary>
/// Main mod class that initializes Harmony patches
/// </summary>
[StaticConstructorOnStartup]
public static class AIImagesMod
{
static AIImagesMod()
{
var harmony = new Harmony("Mrleo1nid.aiimages");
harmony.PatchAll();
Log.Message("[AI Images] Mod initialized successfully");
}
}
}

View File

@@ -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
{
/// <summary>
/// Harmony patch to add a gizmo (button) to all colonist pawns
/// </summary>
[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<Gizmo> Postfix(IEnumerable<Gizmo> __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<Texture2D>.Get("UI/Commands/AttackMelee", true),
action = delegate()
{
// Проверяем, открыто ли уже окно AI Image
Window_AIImage existingWindow = Find
.WindowStack.Windows.OfType<Window_AIImage>()
.FirstOrDefault();
if (existingWindow != null)
{
// Если окно открыто, обновляем пешку
existingWindow.UpdatePawn(__instance);
}
else
{
// Если окна нет, создаём новое
Find.WindowStack.Add(new Window_AIImage(__instance));
}
},
};
}
}
}
}

View File

@@ -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
{
/// <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 "Информация о внешности недоступна";
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();
}
/// <summary>
/// Получает описание одежды персонажа
/// </summary>
private string GetApparelDescription()
{
if (pawn?.apparel == null)
return "Информация об одежде недоступна";
StringBuilder sb = new StringBuilder();
List<Apparel> 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();
}
/// <summary>
/// Форматирует информацию об одном предмете одежды
/// </summary>
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();
}
/// <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;
}
}
}