From 30010078b4ff0553c294de69c679cbcabb23cea9 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sat, 1 Nov 2025 09:15:29 +0300 Subject: [PATCH] Enhance hair color handling in AIImages by adding support for hair color genes in PawnAppearanceData. Update ColorDescriptionService to prioritize gene-based hair color descriptions, improving character appearance representation. Refactor related services to utilize new hair color logic. --- Source/AIImages/Models/PawnAppearanceData.cs | 2 + .../Services/AdvancedPromptGenerator.cs | 117 ++++++++- .../Services/ColorDescriptionService.cs | 228 ++++++++++++++++-- .../Services/PawnDescriptionService.cs | 116 ++++++++- Source/AIImages/Window_AIImage.cs | 2 +- 5 files changed, 431 insertions(+), 34 deletions(-) diff --git a/Source/AIImages/Models/PawnAppearanceData.cs b/Source/AIImages/Models/PawnAppearanceData.cs index a124ce7..4b66f39 100644 --- a/Source/AIImages/Models/PawnAppearanceData.cs +++ b/Source/AIImages/Models/PawnAppearanceData.cs @@ -16,6 +16,7 @@ namespace AIImages.Models public string BodyType { get; set; } public Color SkinColor { get; set; } public List SkinColorGeneDefNames { get; set; } + public List HairColorGeneDefNames { get; set; } public string HairStyle { get; set; } public string HairDefName { get; set; } public Color HairColor { get; set; } @@ -27,6 +28,7 @@ namespace AIImages.Models Traits = new List(); Apparel = new List(); SkinColorGeneDefNames = new List(); + HairColorGeneDefNames = new List(); } } diff --git a/Source/AIImages/Services/AdvancedPromptGenerator.cs b/Source/AIImages/Services/AdvancedPromptGenerator.cs index 2852531..7f334be 100644 --- a/Source/AIImages/Services/AdvancedPromptGenerator.cs +++ b/Source/AIImages/Services/AdvancedPromptGenerator.cs @@ -253,24 +253,24 @@ namespace AIImages.Services case "Skin_Green": return "green skin"; default: - // Пытаемся определить по названию - if (geneDefName.Contains("Green") || geneDefName.Contains("зел")) + // Пытаемся определить по названию (defName всегда на английском) + if (geneDefName.Contains("Green")) return "green skin"; - if (geneDefName.Contains("Blue") || geneDefName.Contains("син")) + if (geneDefName.Contains("Blue")) return "blue skin"; - if (geneDefName.Contains("Red") || geneDefName.Contains("красн")) + if (geneDefName.Contains("Red")) return "red skin"; - if (geneDefName.Contains("Yellow") || geneDefName.Contains("жёлт")) + if (geneDefName.Contains("Yellow")) return "yellow skin"; - if (geneDefName.Contains("Purple") || geneDefName.Contains("фиолет")) + if (geneDefName.Contains("Purple")) return "purple skin"; - if (geneDefName.Contains("Orange") || geneDefName.Contains("оранж")) + if (geneDefName.Contains("Orange")) return "orange skin"; - if (geneDefName.Contains("Black") || geneDefName.Contains("чёрн")) + if (geneDefName.Contains("Black")) return "black skin"; - if (geneDefName.Contains("White") || geneDefName.Contains("бел")) + if (geneDefName.Contains("White")) return "white skin"; - if (geneDefName.Contains("Gray") || geneDefName.Contains("Grey") || geneDefName.Contains("сер")) + if (geneDefName.Contains("Gray") || geneDefName.Contains("Grey")) return "gray skin"; return null; } @@ -283,8 +283,13 @@ namespace AIImages.Services StringBuilder hair = new StringBuilder(); - // Цвет волос - string hairColor = ColorDescriptionService.GetHairColorDescription(data.HairColor); + // Цвет волос - сначала проверяем гены + string hairColor = GetHairColorFromData(data); + if (string.IsNullOrEmpty(hairColor)) + { + // Если нет генов, используем цвет из RGB + hairColor = ColorDescriptionService.GetHairColorDescription(data.HairColor); + } hair.Append(hairColor); hair.Append(" hair, "); @@ -299,6 +304,94 @@ namespace AIImages.Services return hair.ToString(); } + /// + /// Получает описание цвета волос из генов в данных персонажа + /// + private string GetHairColorFromData(PawnAppearanceData data) + { + if (data?.HairColorGeneDefNames == null || !data.HairColorGeneDefNames.Any()) + return null; + + // Берем первый ген (они уже отсортированы по приоритету) + string geneDefName = data.HairColorGeneDefNames.FirstOrDefault(); + if (string.IsNullOrEmpty(geneDefName)) + return null; + + // Определяем цвет волос по названию гена + return GetHairColorFromGeneDefName(geneDefName); + } + + /// + /// Получает описание цвета волос из названия гена + /// + private string GetHairColorFromGeneDefName(string geneDefName) + { + if (string.IsNullOrEmpty(geneDefName)) + return null; + + // Специфичные описания для известных генов цвета волос + switch (geneDefName) + { + case "Hair_Blond": + case "Hair_Blonde": + return "blond"; + case "Hair_Brown": + return "brown"; + case "Hair_Black": + return "black"; + case "Hair_White": + return "white"; + case "Hair_Red": + return "red"; + case "Hair_Ginger": + return "ginger"; + case "Hair_Auburn": + return "auburn"; + case "Hair_Copper": + case "Hair_CopperBrown": + return "copper-brown"; + case "Hair_Light": + return "light"; + case "Hair_Dark": + return "dark"; + default: + // Пытаемся определить по названию (defName всегда на английском) + if (geneDefName.Contains("Blond")) + return "blond"; + if (geneDefName.Contains("Brown")) + return "brown"; + if (geneDefName.Contains("Black")) + return "black"; + if (geneDefName.Contains("White")) + return "white"; + if (geneDefName.Contains("Red")) + return "red"; + if (geneDefName.Contains("Ginger")) + return "ginger"; + if (geneDefName.Contains("Auburn")) + return "auburn"; + if (geneDefName.Contains("Copper")) + return "copper-brown"; + if (geneDefName.Contains("Orange")) + return "orange-red"; + if (geneDefName.Contains("Light")) + return "light"; + if (geneDefName.Contains("Dark")) + return "dark"; + if (geneDefName.Contains("Blue")) + return "blue"; + if (geneDefName.Contains("Green")) + return "green"; + if (geneDefName.Contains("Purple")) + return "purple"; + if (geneDefName.Contains("Pink")) + return "pink"; + if (geneDefName.Contains("Gray") || geneDefName.Contains("Grey")) + return "gray"; + return null; + } + } + private string GetMoodFromTraits(List traits) { if (traits == null || !traits.Any()) diff --git a/Source/AIImages/Services/ColorDescriptionService.cs b/Source/AIImages/Services/ColorDescriptionService.cs index 566f0bc..76b6725 100644 --- a/Source/AIImages/Services/ColorDescriptionService.cs +++ b/Source/AIImages/Services/ColorDescriptionService.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Reflection; using RimWorld; using UnityEngine; using Verse; @@ -12,6 +13,35 @@ namespace AIImages.Services { /// /// Получает текстовое описание цвета волос + /// Сначала проверяет гены цвета волос, затем использует вычисленный RimWorld цвет волос + /// + public static string GetHairColorDescription(Pawn pawn) + { + if (pawn?.genes?.GenesListForReading != null) + { + // Ищем активные и неподавленные гены цвета волос + var hairColorGenes = pawn + .genes.GenesListForReading.Where(gene => + gene.Active && !IsGeneSuppressed(pawn, gene) && IsHairColorGene(gene.def) + ) + .ToList(); + + // Если есть гены цвета волос, используем их описание + if (hairColorGenes.Any()) + { + var hairColorGene = hairColorGenes[0]; + string geneDescription = GetHairColorGeneDescription(hairColorGene.def); + if (!string.IsNullOrEmpty(geneDescription)) + return geneDescription; + } + } + + // Иначе используем вычисленный RimWorld цвет волос + return GetHairColorDescription(pawn?.story?.HairColor ?? Color.white); + } + + /// + /// Получает текстовое описание цвета волос по RGB цвету (fallback метод) /// public static string GetHairColorDescription(Color color) { @@ -43,10 +73,10 @@ namespace AIImages.Services if (pawn?.genes?.GenesListForReading == null) return GetSkinToneDescription(pawn.story.SkinColor); - // Ищем активные гены цвета кожи (особенно специальные: зелёная, синяя, красная и т.д.) + // Ищем активные и неподавленные гены цвета кожи (особенно специальные: зелёная, синяя, красная и т.д.) var skinColorGenes = pawn .genes.GenesListForReading.Where(gene => - gene.Active && IsSpecialSkinColorGene(gene.def) + gene.Active && !IsGeneSuppressed(pawn, gene) && IsSpecialSkinColorGene(gene.def) ) .ToList(); @@ -66,6 +96,7 @@ namespace AIImages.Services /// /// Проверяет, является ли ген специальным геном цвета кожи (зелёная, синяя, красная и т.д.) + /// Исключает гены меланина (Melanin1-9), так как они определяют обычный цвет кожи /// private static bool IsSpecialSkinColorGene(GeneDef geneDef) { @@ -74,6 +105,10 @@ namespace AIImages.Services string geneDefName = geneDef.defName; + // Исключаем гены меланина - они не дают специальный цвет + if (geneDefName.StartsWith("Skin_Melanin")) + return false; + // Специальные цвета: зелёная, синяя, красная, жёлтая, фиолетовая, оранжевая, чёрная, белая, серая return geneDefName.StartsWith("Skin_") && ( @@ -127,33 +162,194 @@ namespace AIImages.Services case "Skin_Green": return "green skin"; default: - // Пытаемся определить по названию - if (geneDefName.Contains("Green") || geneDefName.Contains("зел")) + // Пытаемся определить по названию (defName всегда на английском) + if (geneDefName.Contains("Green")) return "green skin"; - if (geneDefName.Contains("Blue") || geneDefName.Contains("син")) + if (geneDefName.Contains("Blue")) return "blue skin"; - if (geneDefName.Contains("Red") || geneDefName.Contains("красн")) + if (geneDefName.Contains("Red")) return "red skin"; - if (geneDefName.Contains("Yellow") || geneDefName.Contains("жёлт")) + if (geneDefName.Contains("Yellow")) return "yellow skin"; - if (geneDefName.Contains("Purple") || geneDefName.Contains("фиолет")) + if (geneDefName.Contains("Purple")) return "purple skin"; - if (geneDefName.Contains("Orange") || geneDefName.Contains("оранж")) + if (geneDefName.Contains("Orange")) return "orange skin"; - if (geneDefName.Contains("Black") || geneDefName.Contains("чёрн")) + if (geneDefName.Contains("Black")) return "black skin"; - if (geneDefName.Contains("White") || geneDefName.Contains("бел")) + if (geneDefName.Contains("White")) return "white skin"; - if ( - geneDefName.Contains("Gray") - || geneDefName.Contains("Grey") - || geneDefName.Contains("сер") - ) + if (geneDefName.Contains("Gray") || geneDefName.Contains("Grey")) return "gray skin"; return null; } } + /// + /// /// Проверяет, подавлен ли ген другим активным геном + /// + private static bool IsGeneSuppressed(Pawn pawn, Gene gene) + { + if (pawn?.genes == null || gene?.def == null) + return false; + + // В RimWorld, если ген активен, он уже учтён в системе + // Но мы можем проверить, подавляет ли другой активный ген данный ген + // Используем GeneUtility для проверки подавления + try + { + // Проверяем все активные гены на предмет подавления данного гена + foreach (var otherGene in pawn.genes.GenesListForReading) + { + if (otherGene.Active && otherGene != gene && otherGene.def != null) + { + // Проверяем через GeneUtility, если доступен + // Или проверяем свойство suppresses через reflection, если оно есть + 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; + } + } + } + } + } + catch + { + // Если reflection не работает, возвращаем false + // В RimWorld свойство Active уже учитывает подавление + return false; + } + + return false; + } + + /// + /// Проверяет, является ли ген геном цвета волос + /// + private static 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 static string GetHairColorGeneDescription(GeneDef geneDef) + { + if (geneDef == null) + return null; + + string geneDefName = geneDef.defName; + + // Специфичные описания для известных генов цвета волос + switch (geneDefName) + { + case "Hair_Blond": + case "Hair_Blonde": + return "blond hair"; + case "Hair_Brown": + return "brown hair"; + case "Hair_Black": + return "black hair"; + case "Hair_White": + return "white hair"; + case "Hair_Red": + return "red hair"; + case "Hair_Ginger": + return "ginger hair"; + case "Hair_Auburn": + return "auburn hair"; + case "Hair_Copper": + case "Hair_CopperBrown": + return "copper-brown hair"; + case "Hair_Light": + return "light hair"; + case "Hair_Dark": + return "dark hair"; + default: + // Пытаемся определить по названию (defName всегда на английском) + // Добавляем поддержку конкретных генов из RimWorld + if (geneDefName.Contains("Blond")) + return "blond hair"; + if (geneDefName.Contains("Brown")) + return "brown hair"; + if (geneDefName.Contains("Black")) + return "black hair"; + if (geneDefName.Contains("White")) + return "white hair"; + if (geneDefName.Contains("Red")) + return "red hair"; + if (geneDefName.Contains("Ginger")) + return "ginger hair"; + if (geneDefName.Contains("Auburn")) + return "auburn hair"; + if (geneDefName.Contains("Copper")) + return "copper-brown hair"; + if (geneDefName.Contains("Orange")) + return "orange-red hair"; + if (geneDefName.Contains("Light")) + return "light hair"; + if (geneDefName.Contains("Dark")) + return "dark hair"; + if (geneDefName.Contains("Blue")) + return "blue hair"; + if (geneDefName.Contains("Green")) + return "green hair"; + if (geneDefName.Contains("Purple")) + return "purple hair"; + if (geneDefName.Contains("Pink")) + return "pink hair"; + if (geneDefName.Contains("Gray") || geneDefName.Contains("Grey")) + return "gray hair"; + return null; + } + } + /// /// Получает текстовое описание цвета кожи по RGB цвету (fallback метод) /// diff --git a/Source/AIImages/Services/PawnDescriptionService.cs b/Source/AIImages/Services/PawnDescriptionService.cs index 1f1d52b..9417216 100644 --- a/Source/AIImages/Services/PawnDescriptionService.cs +++ b/Source/AIImages/Services/PawnDescriptionService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using AIImages.Models; using RimWorld; @@ -34,13 +35,24 @@ namespace AIImages.Services { var skinColorGenes = pawn .genes.GenesListForReading - .Where(gene => gene.Active && IsSkinColorGene(gene.def)) + .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); + } } // Извлекаем черты характера @@ -110,11 +122,10 @@ namespace AIImages.Services if (pawn.story.hairDef != null) { sb.AppendLine("AIImages.Appearance.Hairstyle".Translate(pawn.story.hairDef.label)); - if (pawn.story.HairColor != null) + // Используем метод с проверкой генов + string hairColorDescription = ColorDescriptionService.GetHairColorDescription(pawn); + if (!string.IsNullOrEmpty(hairColorDescription)) { - string hairColorDescription = ColorDescriptionService.GetHairColorDescription( - pawn.story.HairColor - ); sb.AppendLine( "AIImages.Appearance.HairColorDesc".Translate(hairColorDescription) ); @@ -208,5 +219,100 @@ namespace AIImages.Services || 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; + } } } diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs index 6b5f9e9..c1807d0 100644 --- a/Source/AIImages/Window_AIImage.cs +++ b/Source/AIImages/Window_AIImage.cs @@ -603,7 +603,7 @@ namespace AIImages ("AIImages.Info.Hair".Translate(), appearanceData.HairStyle), ( "AIImages.Info.HairColor".Translate(), - ColorDescriptionService.GetHairColorDescription(appearanceData.HairColor) + ColorDescriptionService.GetHairColorDescription(pawn) ), };