Добавьте файлы проекта.
This commit is contained in:
22
Source/AIImages/AIImages.csproj
Normal file
22
Source/AIImages/AIImages.csproj
Normal 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>
|
||||
19
Source/AIImages/AIImagesMod.cs
Normal file
19
Source/AIImages/AIImagesMod.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Source/AIImages/PawnGizmoPatch.cs
Normal file
71
Source/AIImages/PawnGizmoPatch.cs
Normal 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));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
Source/AIImages/Window_AIImage.cs
Normal file
289
Source/AIImages/Window_AIImage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user