Add Krea portrait models and write appearance as prose.
New schools default to Realism v2.5; living saves keep their catalog. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.Server.Api;
|
||||
|
||||
namespace HSchool.Server.Game;
|
||||
|
||||
/// <summary>Turns a person card into a Flux-style English prompt for SwarmUI.</summary>
|
||||
/// <summary>Turns a person card into an English prompt for SwarmUI.</summary>
|
||||
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,
|
||||
@@ -22,26 +26,8 @@ internal static class PortraitPromptBuilder
|
||||
}
|
||||
|
||||
Add(parts, profile.Pose);
|
||||
Add(parts, DescribeSubject(card));
|
||||
parts.Add($"age {card.Age}");
|
||||
|
||||
foreach (var row in card.Body)
|
||||
{
|
||||
Add(parts, $"{row.Label.ToLowerInvariant()} {row.Value.ToLowerInvariant()}");
|
||||
}
|
||||
|
||||
foreach (var item in PortraitVisibleWorn.Filter(card.Worn))
|
||||
{
|
||||
var color = item.ColorLabel ?? item.Color;
|
||||
if (!string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
Add(parts, $"wearing {item.Label.ToLowerInvariant()} in {color.ToLowerInvariant()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(parts, $"wearing {item.Label.ToLowerInvariant()}");
|
||||
}
|
||||
}
|
||||
Add(parts, DescribeAppearance(card));
|
||||
Add(parts, DescribeClothing(card));
|
||||
|
||||
var positive = SwarmUiLoraFormatter.AppendEmbedTags(
|
||||
string.Join(", ", parts),
|
||||
@@ -63,6 +49,83 @@ internal static class PortraitPromptBuilder
|
||||
parts.Add(value.Trim());
|
||||
}
|
||||
|
||||
private static string DescribeAppearance(PersonCardResponse card)
|
||||
{
|
||||
var parts = new List<string> { 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)
|
||||
@@ -77,4 +140,36 @@ internal static class PortraitPromptBuilder
|
||||
|
||||
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<string> items) => items.Count switch
|
||||
{
|
||||
1 => items[0],
|
||||
2 => $"{items[0]} and {items[1]}",
|
||||
_ => $"{string.Join(", ", items.Take(items.Count - 1))}, and {items[^1]}",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user