Randomized Markings (take two)#38528
Conversation
markings.demo.mp4demonstration i made for delta-v of various markings using the shader code. none of these markings are included in this PR, but they are very popular in downstreams, and crocodilecarousel and i are interested in bringing them upstream if this is merged |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…on-14 into wizden-randomized-markings
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…on-14 into wizden-randomized-markings
Whiteboy.Fix.1.mp4media confirming this now works with skincoloration stuff :> |
…on-14 into wizden-randomized-markings
…into wizden-randomized-markings
|
changes applied from Space-Wizards-Federation/space-station-14#87 |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…on-14 into wizden-randomized-markings
Markings.7.mp4works with locks |
AJCM-git
left a comment
There was a problem hiding this comment.
Everything but the HumanoidCharacterAppearance changes I approve of, there needs to be some code restructuring done in there
| appearance.EyeColor = (charEditorRandomizeConfig & RandomizeCfg.Eyes) != 0 ? RandomEyes() : baseAppearance.EyeColor; | ||
| appearance.SkinColor = (charEditorRandomizeConfig & RandomizeCfg.Skin) != 0 ? RandomSkin(species) : baseAppearance.SkinColor; |
There was a problem hiding this comment.
RandomEyes and RandomSkin are unused now, i recommend restructuring your code so it calls them in this method like before
There was a problem hiding this comment.
cannot do this as those methods are functionally incompatible with the procedure used to generate color palettes. i initially removed RandomEyes and RandomSkin entirely but left them in just in case to avoid breaking changes, if someone else wants to use them later down the track
There was a problem hiding this comment.
hmmm having the old behavior around could be useful I guess, ill consider it as the PR develops
| /// <param name="sex">Sex.</param> | ||
| /// <returns>A new character appearance with selected values randomized</returns> | ||
| public static HumanoidCharacterAppearance Random(RandomizeCfg charEditorRandomizeConfig, HumanoidCharacterAppearance baseAppearance, ProtoId<SpeciesPrototype> species, Sex sex) | ||
| public static HumanoidCharacterAppearance Random(SpeciesPrototype species, Sex sex, RandomizeCfg? charEditorRandomizeConfig, HumanoidCharacterAppearance? baseAppearance) |
There was a problem hiding this comment.
Param order should be the same as before
There was a problem hiding this comment.
since charEditorRandomizeConfig and baseAppearance are optional values i believe they have to be at the end no? i can include the reorder as a breaking change in the header if needed
There was a problem hiding this comment.
pretty sure they only have to go last if you assign them a default value so it isnt really necessary in this case, also is there any benefit to making RandomizeCfg nullable? had a quick look around and it didnt look like it
There was a problem hiding this comment.
| if (colorPalette.Length != 3) | ||
| throw new ArgumentException($"Palettes must have exactly 3 colours, palette contains {colorPalette.Length} colours"); |
There was a problem hiding this comment.
this wont be necessary if you split the function and take a single pallete per appearance part
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |

About the PR
REQUIRES space-wizards/RobustToolbox#6720
Second attempt at #34765 / #38345 with @widgetbeck's permission and some additional tweaks.
Ensures that randomly generated characters will have markings, and prettier-looking colours.
On the dev side, this also allows markings/species authors to fine-tune random generation of markings on a species, determining the chance of rolling markings on individual categories, or even making some markings rarer than others.
I've given humans, voxes, slimes, vulps, and species that use
TintedHuessome parameters to generate what i consider nicer-looking characters by default. These can all be altered as desired.I've also changed some of the default color parameters in various species markings. These changes are very minorly player-facing as they just change the default colour of the new marking when it's added.
Also ALSO added shader support for character customizable markings. This means that you have the option to make glow in the dark markings, among other things.
ports of:
NUBODY UPDATE: because of the body system overhaul, this was stuck in dev hell for a while as i iterated on it downstream. it's now better described as a port of:
Why / Balance
From the original PR:
Most of the additions to this PR that werent in #34765 are QoL tweaks implemented after a period of playtesting the initial PR downstream, and have been pretty popular.
Marking shaders are another addition that have been very popular on our fork and have seen a lot of use on various downstreams.
MARKING WEIGHTS: okay, bear with me here. These were initially added downstream because the authors of species wanted to include rare markings that had a small chance to roll on ghostrole species (as an example, the cat ears). But they also found another use in guaranteeing that certain species don't have a 60% chance to wind up with the default markings. impstation#2855 has an example of this for the downstream Kodepiia species. check it out
Technical details
Shaders:
MarkingPrototypenow has an additional 'shaders' datafield, which takes a dictionary of two strings: state, and shader name.If this field is undefined, markings will use the default shader.
Shaders are modified in
ApplyMarkings. Unfortunately shader layer methods arent in the system, so we had to grabSpriteComponent. This isnt ideal but I'm not fucking with robust toolbox. In a beautiful world shaders would be attached to sprites directly anywayMarkings:
In addition to modifying
HumanoidCharacterAppearance, Ive added a new partial classHumanoidCharacterAppearance.Randomwhich contains the bulk of helper methods involved in handing random markings for a layer, random color generation, etc. All methods have been commented, but please let me know if more documentation is desired as i have removed a lot of beck's comments in the process of reworking this.Anyway, there's lots going on under the hood here. At the moment when we generate a character, the palette will either be a split complimentary, a triadic complimentary, or a plain complimentary- either way, we get 3 colours, where the base colour used is assigned to skintone and the other 2 become eye and hair colours. When we add a marking to our doll, we will colour each layer of a marking by first checking to see if that layer has a defined
MarkingColoring. If it does, we'll colour it using those rules. Otherwise, we'll pick either the hair or eye color at random.Randomizer lock checks have been moved inside these palette methods.
There are also 2 new
SkinColorationPrototypeparameters. I elaborate on these more below in the breaking changes, but these parameters serve as replacements for upstream behavior that I commented out in the editing of this system.We cycle through organs ->
HumanoidVisualLayersin that organ, then add markings to the organ. EachHumanoidVisualLayerslayer enters a loop where it first checks the layer weight to see if a marking should roll at all (would look pretty chaotic if we always rolled max markings), then gets a random marking from that category. This loop is repeated a number of times equal to the value that layer has in the speciesMarkingLimits.Limit. These markings are all eventually collected in a dictionary which is used to construct our finalHumanoidCharacterAppearance.Currently by default all marking categories will have a 60% chance of rolling, and all the individual markings are evenly weighted. Notable exception is that I've guaranteed humans, dwarves, voxes and slimes will always roll hair using their species yml files. Obviously this can easily be adjusted however.
Why did you do it like this?
I have too many thoughts and feelings about randomization. apologies in advance
Hair and facial hair supposedly now function more similarly to other markings than they did pre-nubody, which is great- theyve always been a weird exception. (Apparently they are however still different enough to require their own generation rules. That's fine.) Fundamentally, not every species will have hair, the same way not every species will have, say, a tail. So it makes sense for nubody to update
HumanoidCharacterAppearanceand remove the requirement that every species generates a hair colour, since they aren't all using it!The best way to assign markings colours is by using the archaic
MarkingColoringsystem, as it has a lot of validation rules which come in handy when importing characters: if the file you import is slightly off, then instead of your character being imported with bright white markings, the markings will use a 'default' colour- your skin tone, the color of other markings, or even a darker variation of your skin tone (this is the tattoo default coloring!). All the values are taken from the colors inHumanoidCharacterAppearance.You might be able to predict the trouble I ran into here by combining these two facts. The issue that comes with this is that we are (using the pre-nubody random system, which i did spend a long time iterating on) generating a palette with 3 tones: skin, hair, and eyes.
However, since
HumanoidCharacterAppearanceno longer stores a hair colour, we are unable to useMarkingColoringto assign markings this colour, which means we are limited to using two colours in our randomly generated palette.THE SOLUTION: Fallback colours.
MarkingColoringhas three datafields - a nullableType, a listFallbackTypes, and a colorFallbackColor. What's supposed to happen is that those three types will get used in descending order, depending on availability- if a type is not provided, you use the fallback type, and if thats not there you use the fallback color. In upstream, however, the nullableTypeis always set toSkinColoring, so these fallbacks rarely if ever get used.What I've done is removed the default value from
Type, and movedSkinColoringtoFallbackTypesif that list is undefined. If the system cannot find aType, it will randomly use either hair or eye color. If it does find a type, it will use that instead!This solution is a bit unorthodox, but its making edits to a 3-year old system which I suspect has outlived its original purpose for marking coloration. It allows users to use yaml to customize what randomization looks like on a per-marking basis, which is my main goal with this PR!
Media
Shaders:
Markings:
current randomization:
Randomdemo-Before.mp4
new randomization:
Markingcolor6.mp4
Markings.7.mp4
Requirements
Breaking changes
Markings now support shaders on individual sprite layers. Please see the associated PR for an example on how to implement this.
In addition, randomly generated characters will now roll markings! The rarity of markings can be altered per species. To modify the chance that a marking will randomly roll for an entire category, use
MarkingsGroupPrototype.Weight. To modify the weight of an individual marking rolling over other markings in that category, useMarkingPrototype.RandomWeight.Species
SkinColorationPrototypesnow have additional optionsRealisticColorsandSquashEyeHairColors, which can be toggled on per prototype to generate more realistic colours. As an example,RealisticColorscan be used for humans to generate only real-world hair and eye colours, whileSquashEyeHairColorscan be used to make vulpkanin fur tones more realistic (by making 'hair' fit the same coloration as 'skin').MarkingColorationnow has its default value forTypeset to null, withSkinColoringbeing moved toFallbackTypes.Some markings have also had their default coloring altered.
🆑 widgetbeck, mqole