diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index e9b1f0a076e..7e08e6dd80b 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -87,13 +87,85 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable(); var markingManager = IoCManager.Resolve(); + // Delta V - Begin + 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; // Delta V var strategy = protoMan.Index(skinType).Strategy; var newSkinColor = strategy.InputType switch @@ -103,7 +175,15 @@ public sealed partial class HumanoidCharacterAppearance : IEquatable strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)), }; - return new HumanoidCharacterAppearance(newEyeColor, newSkinColor, new()); + // 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. + + return EnsureValid( + new HumanoidCharacterAppearance(newEyeColor, newSkinColor, compiledMarkings), + species, + sex); + // Delta V - End } public static Color ClampColor(Color color)