using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using AIImages.Models; using RimWorld; using Verse; namespace AIImages.Services { /// /// Сервис для извлечения и описания данных о внешности персонажа /// 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 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(); } /// /// Проверяет, является ли ген геном цвета кожи /// private bool IsSkinColorGene(GeneDef geneDef) { if (geneDef == null) return false; string geneDefName = geneDef.defName; return geneDefName.StartsWith("Skin_") || geneDefName.Contains("SkinColor") || geneDefName.Contains("SkinTone"); } /// /// Проверяет, является ли ген геном цвета волос /// 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") )); } /// /// Проверяет, подавлен ли ген другим активным геном /// 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 suppressesList) { if (suppressesList.Contains(gene.def)) return true; } else if (suppresses is System.Collections.Generic.IEnumerable 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 suppressesList) { if (suppressesList.Contains(gene.def)) return true; } else if (suppresses is System.Collections.Generic.IEnumerable suppressesEnum) { if (suppressesEnum.Contains(gene.def)) return true; } } } } } catch { // Если reflection не работает, возвращаем false // В RimWorld свойство Active уже учитывает подавление return false; } return false; } } }