diff --git a/Content.Client/Humanoid/HumanoidSystem.cs b/Content.Client/Humanoid/HumanoidSystem.cs index 7051b8711f..125a332dee 100644 --- a/Content.Client/Humanoid/HumanoidSystem.cs +++ b/Content.Client/Humanoid/HumanoidSystem.cs @@ -57,6 +57,7 @@ public sealed class HumanoidSystem : SharedHumanoidSystem profile.Species, customBaseLayers, profile.Appearance.SkinColor, + profile.Sex, new(), // doesn't exist yet markings.GetForwardEnumerator().ToList()); } diff --git a/Content.Client/Humanoid/HumanoidVisualizerSystem.cs b/Content.Client/Humanoid/HumanoidVisualizerSystem.cs index 8c50e543bf..808a5a1ba7 100644 --- a/Content.Client/Humanoid/HumanoidVisualizerSystem.cs +++ b/Content.Client/Humanoid/HumanoidVisualizerSystem.cs @@ -36,7 +36,9 @@ public sealed class HumanoidVisualizerSystem : VisualizerSystem))] - public readonly HashSet IgnoredSpecies = new(); } diff --git a/Content.Server/Humanoid/Systems/HumanoidSystem.cs b/Content.Server/Humanoid/Systems/HumanoidSystem.cs index 74ba3adc87..81146afc2a 100644 --- a/Content.Server/Humanoid/Systems/HumanoidSystem.cs +++ b/Content.Server/Humanoid/Systems/HumanoidSystem.cs @@ -39,6 +39,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem component.Species, component.CustomBaseLayers, component.SkinColor, + component.Sex, component.AllHiddenLayers.ToList(), component.CurrentMarkings.GetForwardEnumerator().ToList()); } @@ -50,19 +51,20 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem return; } - SetSpecies(uid, humanoid.Species, false, humanoid); - - if (!string.IsNullOrEmpty(humanoid.Initial) - && _prototypeManager.TryIndex(humanoid.Initial, out HumanoidProfilePrototype? startingSet)) + if (string.IsNullOrEmpty(humanoid.Initial) + || !_prototypeManager.TryIndex(humanoid.Initial, out HumanoidProfilePrototype? startingSet)) { - // Do this first, because profiles currently do not support custom base layers - foreach (var (layer, info) in startingSet.CustomBaseLayers) - { - humanoid.CustomBaseLayers.Add(layer, info); - } - - LoadProfile(uid, startingSet.Profile, humanoid); + LoadProfile(uid, HumanoidCharacterProfile.DefaultWithSpecies(humanoid.Species), humanoid); + return; } + + // Do this first, because profiles currently do not support custom base layers + foreach (var (layer, info) in startingSet.CustomBaseLayers) + { + humanoid.CustomBaseLayers.Add(layer, info); + } + + LoadProfile(uid, startingSet.Profile, humanoid); } private void OnExamined(EntityUid uid, HumanoidComponent component, ExaminedEvent args) diff --git a/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs b/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs index a05cd05227..fe183d6449 100644 --- a/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs +++ b/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs @@ -17,14 +17,14 @@ public sealed class RandomHumanoidAppearanceSystem : EntitySystem private void OnMapInit(EntityUid uid, RandomHumanoidAppearanceComponent component, MapInitEvent args) { // If we have an initial profile/base layer set, do not randomize this humanoid. - if (TryComp(uid, out HumanoidComponent? humanoid) && !string.IsNullOrEmpty(humanoid.Initial)) + if (!TryComp(uid, out HumanoidComponent? humanoid) || !string.IsNullOrEmpty(humanoid.Initial)) { return; } - var profile = HumanoidCharacterProfile.Random(component.IgnoredSpecies); + var profile = HumanoidCharacterProfile.RandomWithSpecies(humanoid.Species); - _humanoid.LoadProfile(uid, profile); + _humanoid.LoadProfile(uid, profile, humanoid); if (component.RandomizeName) { diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index 3709f72e58..6c03ec746c 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -97,6 +97,28 @@ namespace Content.Shared.Humanoid ); } + public static HumanoidCharacterAppearance DefaultWithSpecies(string species) + { + var speciesPrototype = IoCManager.Resolve().Index(species); + var skinColor = speciesPrototype.SkinColoration switch + { + HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone), + HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone, + HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone), + _ => Humanoid.SkinColor.ValidHumanSkinTone + }; + + return new( + HairStyles.DefaultHairStyle, + Color.Black, + HairStyles.DefaultFacialHairStyle, + Color.Black, + Color.Black, + skinColor, + new () + ); + } + private static IReadOnlyList RealisticEyeColors = new List { Color.Brown, diff --git a/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs b/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs index 1124d80bf2..ecfd5d479c 100644 --- a/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs +++ b/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs @@ -12,11 +12,12 @@ public enum HumanoidVisualizerKey [Serializable, NetSerializable] public sealed class HumanoidVisualizerData : ICloneable { - public HumanoidVisualizerData(string species, Dictionary customBaseLayerInfo, Color skinColor, List layerVisibility, List markings) + public HumanoidVisualizerData(string species, Dictionary customBaseLayerInfo, Color skinColor, Sex sex, List layerVisibility, List markings) { Species = species; CustomBaseLayerInfo = customBaseLayerInfo; SkinColor = skinColor; + Sex = sex; LayerVisibility = layerVisibility; Markings = markings; } @@ -24,11 +25,12 @@ public sealed class HumanoidVisualizerData : ICloneable public string Species { get; } public Dictionary CustomBaseLayerInfo { get; } public Color SkinColor { get; } + public Sex Sex { get; } public List LayerVisibility { get; } public List Markings { get; } public object Clone() { - return new HumanoidVisualizerData(Species, new(CustomBaseLayerInfo), SkinColor, new(LayerVisibility), new(Markings)); + return new HumanoidVisualizerData(Species, new(CustomBaseLayerInfo), SkinColor, Sex, new(LayerVisibility), new(Markings)); } } diff --git a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs index d5636ac48b..cf1f3ebb3f 100644 --- a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs +++ b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs @@ -44,6 +44,19 @@ public sealed class SpeciesPrototype : IPrototype [DataField("sprites")] public string SpriteSet { get; } = default!; + /// + /// Default skin tone for this species. This applies for non-human skin tones. + /// + [DataField("defaultSkinTone")] + public Color DefaultSkinTone { get; } = Color.White; + + /// + /// Default human skin tone for this species. This applies for human skin tones. + /// See for the valid range of skin tones. + /// + [DataField("defaultHumanSkinTone")] + public int DefaultHumanSkinTone { get; } = 20; + /// /// The limit of body markings that you can place on this species. /// diff --git a/Content.Shared/Humanoid/SharedHumanoidSystem.cs b/Content.Shared/Humanoid/SharedHumanoidSystem.cs index 342f1d8ef3..423e3e117e 100644 --- a/Content.Shared/Humanoid/SharedHumanoidSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidSystem.cs @@ -24,10 +24,11 @@ public abstract class SharedHumanoidSystem : EntitySystem string species, Dictionary customBaseLayer, Color skinColor, + Sex sex, List visLayers, List markings) { - var data = new HumanoidVisualizerData(species, customBaseLayer, skinColor, visLayers, markings); + var data = new HumanoidVisualizerData(species, customBaseLayer, skinColor, sex, visLayers, markings); // Locally raise an event for this, because there might be some systems interested // in this. diff --git a/Content.Shared/Humanoid/SkinColor.cs b/Content.Shared/Humanoid/SkinColor.cs index 75d24e9e92..c3c1fdb509 100644 --- a/Content.Shared/Humanoid/SkinColor.cs +++ b/Content.Shared/Humanoid/SkinColor.cs @@ -15,11 +15,10 @@ public static class SkinColor } /// - /// Get a human skin tone based on a scale of 0 to 100. + /// Get a human skin tone based on a scale of 0 to 100. The value is clamped between 0 and 100. /// /// Skin tone. Valid range is 0 to 100, inclusive. 0 is gold/yellowish, 100 is dark brown. /// A human skin tone. - /// Exception if the value is under 0 or over 100. public static Color HumanSkinTone(int tone) { // 0 - 100, 0 being gold/yellowish and 100 being dark @@ -31,10 +30,7 @@ public static class SkinColor // 20 is 25 - 20 - 100 // 100 is 25 - 100 - 20 - if (tone < 0 || tone > 100) - { - throw new ArgumentException("Skin tone value was under 0 or over 100."); - } + tone = Math.Clamp(tone, 0, 100); var rangeOffset = tone - 20; diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index b5fe13a943..14af731c6a 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -99,6 +99,11 @@ namespace Content.Shared.Preferences { } + /// + /// Get the default humanoid character profile, using internal constant values. + /// Defaults to for the species. + /// + /// public static HumanoidCharacterProfile Default() { return new( @@ -120,6 +125,33 @@ namespace Content.Shared.Preferences new List()); } + /// + /// Return a default character profile, based on species. + /// + /// The species to use in this default profile. The default species is . + /// Humanoid character profile with default settings. + public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) + { + return new( + "John Doe", + "", + species, + MinimumAge, + Sex.Male, + Gender.Male, + HumanoidCharacterAppearance.DefaultWithSpecies(species), + ClothingPreference.Jumpsuit, + BackpackPreference.Backpack, + new Dictionary + { + {SharedGameTicker.FallbackOverflowJob, JobPriority.High} + }, + PreferenceUnavailableMode.SpawnAsOverflow, + new List(), + new List()); + } + + // TODO: This should eventually not be a visual change only. public static HumanoidCharacterProfile Random(HashSet? ignoredSpecies = null) { var prototypeManager = IoCManager.Resolve(); @@ -130,6 +162,15 @@ namespace Content.Shared.Preferences .Where(x => ignoredSpecies == null ? x.RoundStart : x.RoundStart && !ignoredSpecies.Contains(x.ID)) .ToArray() ).ID; + + return RandomWithSpecies(species); + } + + public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) + { + var prototypeManager = IoCManager.Resolve(); + var random = IoCManager.Resolve(); + var sex = random.Prob(0.5f) ? Sex.Male : Sex.Female; var gender = sex == Sex.Male ? Gender.Male : Gender.Female; diff --git a/Resources/Prototypes/Species/reptilian.yml b/Resources/Prototypes/Species/reptilian.yml index 879b72f25a..8186ce58c1 100644 --- a/Resources/Prototypes/Species/reptilian.yml +++ b/Resources/Prototypes/Species/reptilian.yml @@ -4,6 +4,7 @@ roundStart: true prototype: MobReptilian sprites: MobReptilianSprites + defaultSkinTone: "#34a223" markingLimits: MobReptilianMarkingLimits dollPrototype: MobReptilianDummy skinColoration: Hues diff --git a/Resources/Prototypes/Species/slime.yml b/Resources/Prototypes/Species/slime.yml index 832f997d1c..5879ce6a64 100644 --- a/Resources/Prototypes/Species/slime.yml +++ b/Resources/Prototypes/Species/slime.yml @@ -4,6 +4,7 @@ roundStart: true prototype: MobSlimePerson sprites: MobSlimeSprites + defaultSkinTone: "#b8b8b8" markingLimits: MobSlimeMarkingLimits dollPrototype: MobSlimePersonDummy skinColoration: Hues