Skip to content

Randomized Markings (take two)#38528

Open
mqole wants to merge 38 commits into
space-wizards:masterfrom
mqole:wizden-randomized-markings
Open

Randomized Markings (take two)#38528
mqole wants to merge 38 commits into
space-wizards:masterfrom
mqole:wizden-randomized-markings

Conversation

@mqole

@mqole mqole commented Jun 23, 2025

Copy link
Copy Markdown
Contributor

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 TintedHues some 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:

Randomized characters have always felt very lacking, because they were - All species without hair would generate looking identical and flat. This fixes that.

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:

MarkingPrototype now 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 grab SpriteComponent. This isnt ideal but I'm not fucking with robust toolbox. In a beautiful world shaders would be attached to sprites directly anyway

Markings:

In addition to modifying HumanoidCharacterAppearance, Ive added a new partial class HumanoidCharacterAppearance.Random which 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 SkinColorationPrototype parameters. 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 -> HumanoidVisualLayers in that organ, then add markings to the organ. Each HumanoidVisualLayers layer 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 species MarkingLimits.Limit. These markings are all eventually collected in a dictionary which is used to construct our final HumanoidCharacterAppearance.

Only real quirks are:

  • Slime People have a tendency to look bad because their arm and leg gradients can roll without the corresponding hand/foot markings. I looked into fixing that, but determined it was out of scope for this PR.

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 HumanoidCharacterAppearance and 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 MarkingColoring system, 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 in HumanoidCharacterAppearance.

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 HumanoidCharacterAppearance no longer stores a hair colour, we are unable to use MarkingColoring to assign markings this colour, which means we are limited to using two colours in our randomly generated palette.

THE SOLUTION: Fallback colours. MarkingColoring has three datafields - a nullable Type, a list FallbackTypes, and a color FallbackColor. 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 nullable Type is always set to SkinColoring, so these fallbacks rarely if ever get used.

What I've done is removed the default value from Type, and moved SkinColoring to FallbackTypes if that list is undefined. If the system cannot find a Type, 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:

image

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, use MarkingPrototype.RandomWeight.

Species SkinColorationPrototypes now have additional options RealisticColors and SquashEyeHairColors, which can be toggled on per prototype to generate more realistic colours. As an example, RealisticColors can be used for humans to generate only real-world hair and eye colours, while SquashEyeHairColors can be used to make vulpkanin fur tones more realistic (by making 'hair' fit the same coloration as 'skin').

MarkingColoration now has its default value for Type set to null, with SkinColoring being moved to FallbackTypes.

Some markings have also had their default coloring altered.

🆑 widgetbeck, mqole

  • add: Randomized characters will now roll markings.
  • add: Added a 'Lock Markings' button to the character randomizer settings.

@github-actions github-actions Bot added Changes: UI Changes: Might require knowledge of UI design or code. S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. size/M Denotes a PR that changes 100-999 lines. labels Jun 23, 2025
@hivehum

hivehum commented Jun 23, 2025

Copy link
Copy Markdown
Contributor
markings.demo.mp4

demonstration 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

@github-actions github-actions Bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jul 4, 2025
@github-actions

github-actions Bot commented Jul 4, 2025

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions Bot added S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted and removed S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted labels Jul 4, 2025
@github-actions

github-actions Bot commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions Bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jul 8, 2025
@ToastEnjoyer ToastEnjoyer added P3: Standard Priority: Default priority for repository items. T: New Feature Type: New feature or content, or extending existing content D3: Low Difficulty: Some codebase knowledge required. A: Character/Species Area: Player characters and species features and content. and removed S: Untriaged Status: Indicates an item has not been triaged and doesn't have appropriate labels. labels Aug 6, 2025
@iaada iaada added the S: Needs Review Status: Requires additional reviews before being fully accepted. Not to be replaced by S: Approved. label Aug 12, 2025
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions Bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Sep 14, 2025
@mqole

mqole commented Oct 18, 2025

Copy link
Copy Markdown
Contributor Author
Whiteboy.Fix.1.mp4

media confirming this now works with skincoloration stuff :>
apologies for the delay, took a bit to figure this out lol

@github-actions github-actions Bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Oct 18, 2025
@github-actions github-actions Bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jun 30, 2026
@mqole

mqole commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

changes applied from Space-Wizards-Federation/space-station-14#87

@AJCM-git AJCM-git left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be aware that ill merge #44363 first. Should add the locks to markings too if possible

Comment thread Content.Shared/Humanoid/SkinColorationPrototype.cs Outdated
Comment thread Content.Client/Body/VisualBodySystem.cs Outdated
Comment thread Content.Shared/Humanoid/Markings/MarkingPrototype.cs
Comment thread Content.Shared/Humanoid/HumanoidCharacterAppearance.Markings.cs Outdated
@github-actions github-actions Bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jul 3, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@github-actions github-actions Bot removed the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jul 4, 2026
@mqole

mqole commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author
Markings.7.mp4

works with locks

@AJCM-git AJCM-git left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything but the HumanoidCharacterAppearance changes I approve of, there needs to be some code restructuring done in there

Comment on lines -147 to -148
appearance.EyeColor = (charEditorRandomizeConfig & RandomizeCfg.Eyes) != 0 ? RandomEyes() : baseAppearance.EyeColor;
appearance.SkinColor = (charEditorRandomizeConfig & RandomizeCfg.Skin) != 0 ? RandomSkin(species) : baseAppearance.SkinColor;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RandomEyes and RandomSkin are unused now, i recommend restructuring your code so it calls them in this method like before

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AJCM-git AJCM-git Jul 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm having the old behavior around could be useful I guess, ill consider it as the PR develops

Comment thread Content.Shared/Humanoid/SkinColorationPrototype.cs Outdated
Comment thread Content.Shared/Humanoid/HumanoidCharacterAppearance.cs Outdated
/// <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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Param order should be the same as before

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@mqole mqole Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to give them defaults oops
image

benefit for making RandomizeCfg nullable is to allow a humanoidappearance to be completely randomized without needing cfg settings, i was thinking this could be a nice method to use for something like DNA scrambler or changeling

Comment thread Content.Shared/Humanoid/HumanoidCharacterAppearance.cs Outdated
Comment thread Content.Shared/Humanoid/HumanoidCharacterAppearance.Markings.cs Outdated
Comment thread Content.Shared/Humanoid/HumanoidCharacterAppearance.Markings.cs Outdated
Comment on lines +50 to +51
if (colorPalette.Length != 3)
throw new ArgumentException($"Palettes must have exactly 3 colours, palette contains {colorPalette.Length} colours");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wont be necessary if you split the function and take a single pallete per appearance part

@mqole mqole requested a review from AJCM-git July 6, 2026 14:46
@github-actions github-actions Bot added the S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted label Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A: Character/Species Area: Player characters and species features and content. Changes: UI Changes: Might require knowledge of UI design or code. D3: Low Difficulty: Some codebase knowledge required. P2: Raised Priority: Item has a raised priority, indicating it might get increased maintainer attention. S: Merge Conflict Status: Needs to resolve merge conflicts before it can be accepted S: Needs Review Status: Requires additional reviews before being fully accepted. Not to be replaced by S: Approved. size/M Denotes a PR that changes 100-999 lines. T: New Feature Type: New feature or content, or extending existing content

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants