From 82ddbfac0aa81b1f9a8b26d1328cbf1e5afc3010 Mon Sep 17 00:00:00 2001 From: EWanderer <6307595+Ewanderer@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:18:05 +0200 Subject: [PATCH 1/2] randomize markings, when building a random character appearance. --- .../Humanoid/HumanoidCharacterAppearance.cs | 115 ++++++++++++++++-- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index e9b1f0a076e..70a13dd9287 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -21,7 +21,11 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable, Dictionary>> Markings { get; set; } = new(); + public Dictionary, Dictionary>> Markings + { + get; + set; + } = new(); public HumanoidCharacterAppearance( Color eyeColor, @@ -36,7 +40,6 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable, Dictionary>> newMarkings) + public HumanoidCharacterAppearance WithMarkings( + Dictionary, Dictionary>> newMarkings) { return new(EyeColor, SkinColor, newMarkings); } @@ -87,23 +91,102 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable(); var markingManager = IoCManager.Resolve(); + var protoMan = IoCManager.Resolve(); + var speciesPrototype = protoMan.Index(species); - // TODO: Add random markings + Dictionary, Dictionary>> compiledMarkings = + new(); + //build a color pallet for all parts. + var colours = Enumerable.Range(0, 255) + .Select(e => new Color(random.NextByte(), random.NextByte(), random.NextByte())) + .ToArray(); + //most likely list of simple physical traits. + HumanoidVisualLayers[] layerFilter = + [ + HumanoidVisualLayers.Hair, + HumanoidVisualLayers.Tail, + HumanoidVisualLayers.FacialHair, + HumanoidVisualLayers.Fire, + HumanoidVisualLayers.Snout, + ]; + + //build organ for organ + foreach (var organ in markingManager.GetOrgans(species)) + { + //get the marking data for that organ + if (!markingManager.TryGetMarkingData(organ.Value, out var organMarkingData)) + continue; + //extract the group based on the organ + var group = protoMan.Index(organMarkingData.Value.Group.Id); + // setup an empty dictionary of layers + compiledMarkings[organ.Key] = new(); + //layer for layer. + foreach (var layer in organMarkingData.Value.Layers) + { + //only randomize physical traits. + if(!layerFilter.Contains(layer)) + continue; + //get all markings for that layer, sex, group and flatten to markings. + var markings = + markingManager.MarkingsByLayerAndGroupAndSex(layer, organMarkingData.Value.Group, sex) + .Select(e => e.Value.AsMarking()) + .ToArray(); + //skip if no matches + if(markings.Length==0) + continue; + //check restrictions for the layer + int limitOfMarking; + if (!group.Limits.TryGetValue(layer, out var limits)) + { + limitOfMarking = markings.Length; + } + else + { + limitOfMarking = limits.Limit; + //blatant skip chance unless required. + if (!limits.Required && limitOfMarking==1 && random.NextDouble() < 0.5) + continue; + } + + //pick random feature list within limit + compiledMarkings[organ.Key][layer] = Enumerable.Range(0,limitOfMarking==1?1:random.Next(limitOfMarking)) + .Select(e => + { + // return random.Pick(markings); + var baseMarking = random.Pick(markings); + for (var i = 0; i < baseMarking.MarkingColors.Count&&i(); - var skinType = protoMan.Index(species).SkinColoration; + var skinType = speciesPrototype.SkinColoration; var strategy = protoMan.Index(skinType).Strategy; var newSkinColor = strategy.InputType switch { SkinColorationStrategyInput.Unary => strategy.FromUnary(random.NextFloat(0f, 100f)), - SkinColorationStrategyInput.Color => strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)), + SkinColorationStrategyInput.Color => strategy.ClosestSkinColor(new Color(random.NextFloat(1), + random.NextFloat(1), + random.NextFloat(1), + 1)), _ => strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)), }; - return new HumanoidCharacterAppearance(newEyeColor, newSkinColor, new()); + // Safety step. Most systems which called Random() also called this, and not doing so caused issues with markings. + // In the future it could *maybe* be removed, but it's probably worth the extra CPU cycles to validate this info. + + return EnsureValid( + new HumanoidCharacterAppearance(newEyeColor, newSkinColor, compiledMarkings), + species, + sex); } public static Color ClampColor(Color color) @@ -111,7 +194,9 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable species, Sex sex) + public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, + ProtoId species, + Sex sex) { var eyeColor = ClampColor(appearance.EyeColor); @@ -146,7 +231,11 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable Date: Sat, 4 Jul 2026 16:15:27 +0200 Subject: [PATCH 2/2] Remove unnecesarry changes to whitespace/linebreak --- .../Humanoid/HumanoidCharacterAppearance.cs | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index 70a13dd9287..7e08e6dd80b 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -21,11 +21,7 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable, Dictionary>> Markings - { - get; - set; - } = new(); + public Dictionary, Dictionary>> Markings { get; set; } = new(); public HumanoidCharacterAppearance( Color eyeColor, @@ -40,6 +36,7 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable, Dictionary>> newMarkings) + public HumanoidCharacterAppearance WithMarkings(Dictionary, Dictionary>> newMarkings) { return new(EyeColor, SkinColor, newMarkings); } @@ -91,6 +87,7 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable(); var markingManager = IoCManager.Resolve(); + // Delta V - Begin var protoMan = IoCManager.Resolve(); var speciesPrototype = protoMan.Index(species); @@ -164,22 +161,21 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable strategy.FromUnary(random.NextFloat(0f, 100f)), - SkinColorationStrategyInput.Color => strategy.ClosestSkinColor(new Color(random.NextFloat(1), - random.NextFloat(1), - random.NextFloat(1), - 1)), + SkinColorationStrategyInput.Color => strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)), _ => strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)), }; + // Delta V - Begin // Safety step. Most systems which called Random() also called this, and not doing so caused issues with markings. // In the future it could *maybe* be removed, but it's probably worth the extra CPU cycles to validate this info. @@ -187,6 +183,7 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable species, - Sex sex) + public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, ProtoId species, Sex sex) { var eyeColor = ClampColor(appearance.EyeColor); @@ -231,11 +226,7 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable