Randomized Markings (#34765)

randomized markings
This commit is contained in:
beck
2025-06-15 17:11:52 -04:00
committed by GitHub
parent ef832141c6
commit b636e3fcba

View File

@@ -126,52 +126,220 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
{ {
var random = IoCManager.Resolve<IRobustRandom>(); var random = IoCManager.Resolve<IRobustRandom>();
var markingManager = IoCManager.Resolve<MarkingManager>(); var markingManager = IoCManager.Resolve<MarkingManager>();
var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList();
var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList();
var newHairStyle = hairStyles.Count > 0 var newFacialHairStyle = HairStyles.DefaultFacialHairStyle;
? random.Pick(hairStyles) var newHairStyle = HairStyles.DefaultHairStyle;
: HairStyles.DefaultHairStyle; List<Marking> newMarkings = [];
var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female // grab a completely random color.
? HairStyles.DefaultFacialHairStyle var baseColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1);
: random.Pick(facialHairStyles);
var newHairColor = random.Pick(HairStyles.RealisticHairColors); // create a new color palette based on BaseColor. roll to determine what type of palette it is.
newHairColor = newHairColor // personally I think this should be weighted, but I can't be bothered to implement that.
.WithRed(RandomizeColor(newHairColor.R)) List<Color> colorPalette = [];
.WithGreen(RandomizeColor(newHairColor.G)) switch (random.Next(3))
.WithBlue(RandomizeColor(newHairColor.B)); {
case 0:
// TODO: Add random markings colorPalette = GetSplitComplementaries(baseColor);
break;
var newEyeColor = random.Pick(RealisticEyeColors); case 1:
colorPalette = GetTriadicComplementaries(baseColor);
break;
case 2:
colorPalette = GetOneComplementary(baseColor);
break;
}
// grab the species skin coloration type.
var skinType = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species).SkinColoration; var skinType = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species).SkinColoration;
var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1); // declare some defaults. ensures that the hair and eyes on hues-colored species don't match the skin or one another.
var newSkinColor = colorPalette[0];
var newHairColor = colorPalette[1];
var newEyeColor = colorPalette[2];
// now we do some color logic.
switch (skinType) switch (skinType)
{ {
// if the species is HumanToned:
case HumanoidSkinColor.HumanToned: case HumanoidSkinColor.HumanToned:
// quantize the randomized skin color to the nearest acceptable HumanToned color.
var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor)); var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor));
newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone); newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone);
// pick a random realistic hair color from the list and randomize it juuuuust a little bit.
newHairColor = random.Pick(HairStyles.RealisticHairColors);
newHairColor = newHairColor
.WithRed(RandomizeColor(newHairColor.R))
.WithGreen(RandomizeColor(newHairColor.G))
.WithBlue(RandomizeColor(newHairColor.B));
// and pick a random realistic eye color from the list.
newEyeColor = random.Pick(RealisticEyeColors);
// we're also going to crush the other colors down to the skin's luminosity so markings don't appear too bright on darker skin.
colorPalette[1] = SquashToSkinLuminosity(newSkinColor, colorPalette[1]);
colorPalette[2] = SquashToSkinLuminosity(newSkinColor, colorPalette[2]);
break; break;
// if the species is Hues toned: it's fine the way it is.
case HumanoidSkinColor.Hues: case HumanoidSkinColor.Hues:
break; break;
// if the species is TintedHues toned: tint them hues.
case HumanoidSkinColor.TintedHues: case HumanoidSkinColor.TintedHues:
newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor); newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor);
// we're also going to crush the other colors down to valid TintedHues skin colors.
colorPalette[1] = Humanoid.SkinColor.ValidTintedHuesSkinTone(colorPalette[1]);
colorPalette[2] = Humanoid.SkinColor.ValidTintedHuesSkinTone(colorPalette[2]);
break; break;
// if the species is VoxFeathers toned: confine the skin color to vox limits. Bright colors are otherwise fine, so leave the marking colors alone.
case HumanoidSkinColor.VoxFeathers: case HumanoidSkinColor.VoxFeathers:
newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor); newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor);
break; break;
} }
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ()); // now we loop through every extant marking category,
foreach (var category in Enum.GetValues<MarkingCategories>())
{
// grab a list of markings in that category for that species,
var markings = markingManager.MarkingsByCategoryAndSpecies(category, species).Keys.ToList();
var markingProtos = markingManager.MarkingsByCategoryAndSpecies(category, species).Values.ToList();
// if it's facial hair, there are entries in the category, and the character is not female, assign a random one. else bald
if (category == MarkingCategories.FacialHair)
{
newFacialHairStyle = markings.Count == 0 || sex == Sex.Female ? HairStyles.DefaultFacialHairStyle : random.Pick(markings);
}
// if it's hair, and there are hair styles, roll one. else bald
else if (category == MarkingCategories.Hair)
{
newHairStyle = markings.Count > 0 ? random.Pick(markings) : HairStyles.DefaultHairStyle;
}
// for every other category,
else if (markings.Count > 0)
{
// roll a die. currently a 1 in 3 chance per category, except Tails, which are 1 in 2 (because of the effect they have on the silhouettes of spiders and moths.)
int diceRoll;
if (category == MarkingCategories.Tail)
diceRoll = random.Next(2);
else
diceRoll = random.Next(3);
if (diceRoll == 0)
{
MarkingPrototype? lastMarking = null;
// roll to see how many markings from that category will be added. currently a maximum of 2.
var loops = random.Next(2) + 1;
// add a marking (loops) times
for (var i = 0; i < loops; i++)
{
// pick a random marking from the list
var protoToAdd = random.Pick(markingProtos);
var markingToAdd = protoToAdd.AsMarking();
Color markingColor;
// prevent duplicates:
if (lastMarking != null && lastMarking == protoToAdd)
continue;
// set gauze to white.
// side note, I really hate that gauze isn't its own category. please fix that so that i can make this not suck as much.
// or, like, give it its own color rules. or something.
if (markingToAdd.MarkingId.Contains("gauze", StringComparison.OrdinalIgnoreCase))
{
markingToAdd.SetColor(Color.White);
newMarkings.Add(markingToAdd);
lastMarking = protoToAdd;
continue;
}
// select a random color from our two secondary colors. if our marking is a Tail, add the skin color as well, otherwise lizards always look a little odd.
// this will also make moths and spiders look less interesting on average, but I don't want a hardcoded exception for lizards.
if (category == MarkingCategories.Tail)
markingColor = random.Pick(colorPalette);
else
markingColor = random.Pick(colorPalette.Skip(0).ToList());
// set the marking to that color
markingToAdd.SetColor(markingColor);
// otherwise, add it to the final list.
newMarkings.Add(markingToAdd);
lastMarking = protoToAdd;
}
}
}
}
// at the end of all that, we should have new values for each of these, so we set the character appearance to these new values.
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, newMarkings);
// helper functions:
float RandomizeColor(float channel) float RandomizeColor(float channel)
{ {
return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f); return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f);
} }
List<Color> GetComplementaryColors(Color color, double angle)
{
var hsl = Color.ToHsl(color);
var hVal = hsl.X + angle;
hVal = hVal >= 0.360 ? hVal - 0.360 : hVal;
var positiveHSL = new Vector4((float)hVal, hsl.Y, hsl.Z, hsl.W);
var hVal1 = hsl.X - angle;
hVal1 = hVal1 <= 0 ? hVal1 + 0.360 : hVal1;
var negativeHSL = new Vector4((float)hVal1, hsl.Y, hsl.Z, hsl.W);
var c0 = Color.FromHsl(positiveHSL);
var c1 = Color.FromHsl(negativeHSL);
var palette = new List<Color> { color, c0, c1 };
return palette;
}
// return a list of triadic complementary colors
List<Color> GetTriadicComplementaries(Color color)
{
return GetComplementaryColors(color, 0.120);
}
// return a list of split complementary colors
List<Color> GetSplitComplementaries(Color color)
{
return GetComplementaryColors(color, 0.150);
}
// return a list containing the base color and two copies of a single complemenary color
List<Color> GetOneComplementary(Color color)
{
return GetComplementaryColors(color, 0.180);
}
Color SquashToSkinLuminosity(Color skinColor, Color toSquash)
{
var skinColorHSL = Color.ToHsl(skinColor);
var toSquashHSL = Color.ToHsl(toSquash);
// check if the skin color is as dark as or darker than the marking color:
if (toSquashHSL.Z <= skinColorHSL.Z)
{
// if it is, don't fuck with it
return toSquash;
}
// otherwise, create a new color with the H, S, and A of toSquash, but the L of skinColor
var newColor = new Vector4(toSquashHSL.X, toSquashHSL.Y, skinColorHSL.Z, toSquashHSL.W);
return Color.FromHsl(newColor);
}
} }
public static Color ClampColor(Color color) public static Color ClampColor(Color color)