using HSchool.Content; using HSchool.Server.Api; namespace HSchool.Server.Game; /// Turns a person card into an English prompt for SwarmUI. internal static class PortraitPromptBuilder { private const string HairColorId = "HairColor"; private const string EyeColorId = "EyeColor"; public static (string Positive, string Negative) Build( PersonCardResponse card, SwarmUiResolvedProfile profile, PortraitKind kind, string? promptExtra = null) { var parts = new List(); Add(parts, profile.ModelPositive); Add(parts, profile.Style); Add(parts, profile.ShotType); if (kind == PortraitKind.Custom) { Add(parts, promptExtra); } Add(parts, profile.Pose); Add(parts, DescribeAppearance(card)); Add(parts, DescribeClothing(card)); var positive = SwarmUiLoraFormatter.AppendEmbedTags( string.Join(", ", parts), profile.PositiveEmbeddings); var negative = SwarmUiLoraFormatter.AppendEmbedTags( profile.Negative.Trim(), profile.NegativeEmbeddings); return (positive, negative); } private static void Add(List parts, string? value) { if (string.IsNullOrWhiteSpace(value)) { return; } parts.Add(value.Trim()); } private static string DescribeAppearance(PersonCardResponse card) { var parts = new List { DescribeSubject(card), $"{card.Age} years old" }; var hair = BodyValue(card, HairColorId); if (hair is not null) { parts.Add($"{hair.ToLowerInvariant()} hair"); } var eyes = BodyValue(card, EyeColorId); if (eyes is not null) { parts.Add($"{eyes.ToLowerInvariant()} eyes"); } var build = BodyValue(card, BodyBuilds.Attribute); if (build is not null && !build.Equals(BodyBuilds.Average, StringComparison.OrdinalIgnoreCase)) { parts.Add($"a {build.ToLowerInvariant()} build"); } var height = BodyValue(card, BodyBuilds.HeightAttribute); if (height is not null) { parts.Add($"{height} cm tall"); } if (parts.Count == 2) { return $"{parts[0]}, {parts[1]}"; } return $"{parts[0]}, {parts[1]}, with {string.Join(", ", parts.Skip(2))}"; } private static string? DescribeClothing(PersonCardResponse card) { var items = PortraitVisibleWorn.Filter(card.Worn) .Select(DescribeWornItem) .Where(item => !string.IsNullOrWhiteSpace(item)) .ToList(); if (items.Count == 0) { return null; } return $"wearing {JoinList(items)}"; } private static string DescribeWornItem(WornItemResponse item) { var noun = item.Label.Trim(); if (string.IsNullOrWhiteSpace(noun)) { noun = item.DefName.Trim(); } noun = noun.ToLowerInvariant(); var color = (item.ColorLabel ?? item.Color)?.Trim(); if (!string.IsNullOrWhiteSpace(color)) { noun = $"{color.ToLowerInvariant()} {noun}"; } var condition = item.ConditionLabel?.Trim().ToLowerInvariant(); noun = condition switch { "torn" => $"torn {noun}", "worn" => $"worn {noun}", "in rags" => $"{noun} in rags", _ => noun, }; return WithArticle(noun); } private static string DescribeSubject(PersonCardResponse card) { if (card.Age <= 11) { return card.Female ? "A young girl" : "A young boy"; } if (card.Age <= 17) { return card.Female ? "A teenage girl" : "A teenage boy"; } return card.Female ? "A young woman" : "A young man"; } private static string? BodyValue(PersonCardResponse card, string id) { foreach (var row in card.Body) { if (row.Id.Equals(id, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(row.Value)) { return row.Value.Trim(); } } return null; } private static string WithArticle(string phrase) { if (phrase.Length == 0) { return phrase; } var first = char.ToLowerInvariant(phrase[0]); var article = first is 'a' or 'e' or 'i' or 'o' or 'u' ? "an" : "a"; return $"{article} {phrase}"; } private static string JoinList(IReadOnlyList items) => items.Count switch { 1 => items[0], 2 => $"{items[0]} and {items[1]}", _ => $"{string.Join(", ", items.Take(items.Count - 1))}, and {items[^1]}", }; }