All checks were successful
SonarQube Analysis / Build and analyze (push) Successful in 1m40s
319 lines
12 KiB
C#
319 lines
12 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using AIImages.Models;
|
||
using RimWorld;
|
||
using Verse;
|
||
|
||
namespace AIImages.Services
|
||
{
|
||
/// <summary>
|
||
/// Сервис для извлечения и описания данных о внешности персонажа
|
||
/// </summary>
|
||
public class PawnDescriptionService : IPawnDescriptionService
|
||
{
|
||
public PawnAppearanceData ExtractAppearanceData(Pawn pawn)
|
||
{
|
||
if (pawn?.story == null)
|
||
return null;
|
||
|
||
var data = new PawnAppearanceData
|
||
{
|
||
Name = pawn.Name?.ToStringShort ?? "Unknown",
|
||
Gender = pawn.gender,
|
||
Age = pawn.ageTracker.AgeBiologicalYears,
|
||
BodyType = pawn.story.bodyType?.defName,
|
||
SkinColor = pawn.story.SkinColor,
|
||
HairStyle = pawn.story.hairDef?.label,
|
||
HairDefName = pawn.story.hairDef?.defName,
|
||
HairColor = pawn.story.HairColor,
|
||
};
|
||
|
||
// Извлекаем гены цвета кожи
|
||
if (pawn.genes?.GenesListForReading != null)
|
||
{
|
||
var skinColorGenes = pawn
|
||
.genes.GenesListForReading
|
||
.Where(gene => gene.Active && IsSkinColorGene(gene.def) && !IsGeneSuppressed(pawn, gene))
|
||
.ToList();
|
||
|
||
foreach (var gene in skinColorGenes)
|
||
{
|
||
data.SkinColorGeneDefNames.Add(gene.def.defName);
|
||
}
|
||
|
||
// Извлекаем гены цвета волос
|
||
var hairColorGenes = pawn
|
||
.genes.GenesListForReading
|
||
.Where(gene => gene.Active && IsHairColorGene(gene.def) && !IsGeneSuppressed(pawn, gene))
|
||
.ToList();
|
||
|
||
foreach (var gene in hairColorGenes)
|
||
{
|
||
data.HairColorGeneDefNames.Add(gene.def.defName);
|
||
}
|
||
}
|
||
|
||
// Извлекаем черты характера
|
||
if (pawn.story.traits?.allTraits != null)
|
||
{
|
||
data.Traits.AddRange(pawn.story.traits.allTraits);
|
||
}
|
||
|
||
// Извлекаем одежду
|
||
if (pawn.apparel?.WornApparel != null)
|
||
{
|
||
foreach (var apparel in pawn.apparel.WornApparel)
|
||
{
|
||
var apparelData = new ApparelData
|
||
{
|
||
Label = apparel.def.label,
|
||
DefName = apparel.def.defName,
|
||
Material = apparel.Stuff?.label,
|
||
MaterialDefName = apparel.Stuff?.defName,
|
||
Color = apparel.DrawColor,
|
||
LayerType = apparel.def.apparel?.LastLayer.ToString(),
|
||
Durability = apparel.HitPoints,
|
||
MaxDurability = apparel.MaxHitPoints,
|
||
};
|
||
|
||
if (apparel.TryGetQuality(out QualityCategory quality))
|
||
{
|
||
apparelData.Quality = quality;
|
||
}
|
||
|
||
data.Apparel.Add(apparelData);
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
public string GetAppearanceDescription(Pawn pawn)
|
||
{
|
||
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)
|
||
);
|
||
}
|
||
|
||
// Цвет кожи (с умным описанием, сначала проверяем гены)
|
||
string skinDescription = ColorDescriptionService.GetSkinToneDescription(pawn);
|
||
if (!string.IsNullOrEmpty(skinDescription))
|
||
{
|
||
sb.AppendLine("AIImages.Appearance.SkinTone".Translate(skinDescription));
|
||
}
|
||
|
||
// Волосы
|
||
if (pawn.story.hairDef != null)
|
||
{
|
||
sb.AppendLine("AIImages.Appearance.Hairstyle".Translate(pawn.story.hairDef.label));
|
||
// Используем метод с проверкой генов
|
||
string hairColorDescription = ColorDescriptionService.GetHairColorDescription(pawn);
|
||
if (!string.IsNullOrEmpty(hairColorDescription))
|
||
{
|
||
sb.AppendLine(
|
||
"AIImages.Appearance.HairColorDesc".Translate(hairColorDescription)
|
||
);
|
||
}
|
||
}
|
||
|
||
// Черты характера
|
||
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();
|
||
}
|
||
|
||
public string GetApparelDescription(Pawn pawn)
|
||
{
|
||
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();
|
||
}
|
||
|
||
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 != UnityEngine.Color.white)
|
||
{
|
||
string colorDesc = ColorDescriptionService.GetApparelColorDescription(
|
||
apparel.DrawColor
|
||
);
|
||
sb.AppendLine("AIImages.Apparel.ColorDesc".Translate(colorDesc));
|
||
}
|
||
|
||
sb.AppendLine();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, является ли ген геном цвета кожи
|
||
/// </summary>
|
||
private bool IsSkinColorGene(GeneDef geneDef)
|
||
{
|
||
if (geneDef == null)
|
||
return false;
|
||
|
||
string geneDefName = geneDef.defName;
|
||
return geneDefName.StartsWith("Skin_")
|
||
|| geneDefName.Contains("SkinColor")
|
||
|| geneDefName.Contains("SkinTone");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, является ли ген геном цвета волос
|
||
/// </summary>
|
||
private bool IsHairColorGene(GeneDef geneDef)
|
||
{
|
||
if (geneDef == null)
|
||
return false;
|
||
|
||
string geneDefName = geneDef.defName;
|
||
return geneDefName.StartsWith("Hair_")
|
||
|| geneDefName.Contains("HairColor")
|
||
|| geneDefName.Contains("HairColour")
|
||
|| geneDefName.Contains("HairTone")
|
||
|| (geneDefName.Contains("Hair") && (
|
||
geneDefName.Contains("Color")
|
||
|| geneDefName.Contains("Colour")
|
||
|| geneDefName.Contains("Red")
|
||
|| geneDefName.Contains("Blue")
|
||
|| geneDefName.Contains("Green")
|
||
|| geneDefName.Contains("Blond")
|
||
|| geneDefName.Contains("Brown")
|
||
|| geneDefName.Contains("Black")
|
||
|| geneDefName.Contains("White")
|
||
|| geneDefName.Contains("Light")
|
||
|| geneDefName.Contains("Dark")
|
||
|| geneDefName.Contains("Ginger")
|
||
|| geneDefName.Contains("Auburn")
|
||
|| geneDefName.Contains("Copper")
|
||
|| geneDefName.Contains("Orange")
|
||
));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, подавлен ли ген другим активным геном
|
||
/// </summary>
|
||
private bool IsGeneSuppressed(Pawn pawn, Gene gene)
|
||
{
|
||
if (pawn?.genes == null || gene?.def == null)
|
||
return false;
|
||
|
||
// В RimWorld, если ген активен, он уже учтён в системе
|
||
// Но мы можем проверить, подавляет ли другой активный ген данный ген
|
||
try
|
||
{
|
||
// Проверяем все активные гены на предмет подавления данного гена
|
||
foreach (var otherGene in pawn.genes.GenesListForReading)
|
||
{
|
||
if (otherGene.Active && otherGene != gene && otherGene.def != null)
|
||
{
|
||
// Проверяем через reflection, так как свойство suppresses может быть приватным
|
||
var suppressesField = otherGene.def.GetType().GetField("suppresses");
|
||
if (suppressesField != null)
|
||
{
|
||
var suppresses = suppressesField.GetValue(otherGene.def);
|
||
if (suppresses is System.Collections.Generic.List<GeneDef> suppressesList)
|
||
{
|
||
if (suppressesList.Contains(gene.def))
|
||
return true;
|
||
}
|
||
else if (suppresses is System.Collections.Generic.IEnumerable<GeneDef> suppressesEnum)
|
||
{
|
||
if (suppressesEnum.Contains(gene.def))
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// Также проверяем свойство, если оно публичное
|
||
var suppressesProperty = otherGene.def.GetType().GetProperty("suppresses");
|
||
if (suppressesProperty != null)
|
||
{
|
||
var suppresses = suppressesProperty.GetValue(otherGene.def);
|
||
if (suppresses is System.Collections.Generic.List<GeneDef> suppressesList)
|
||
{
|
||
if (suppressesList.Contains(gene.def))
|
||
return true;
|
||
}
|
||
else if (suppresses is System.Collections.Generic.IEnumerable<GeneDef> suppressesEnum)
|
||
{
|
||
if (suppressesEnum.Contains(gene.def))
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// Если reflection не работает, возвращаем false
|
||
// В RimWorld свойство Active уже учитывает подавление
|
||
return false;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
}
|