diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
index a01a51ab90..732f674ec6 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
@@ -22,7 +22,7 @@ namespace Content.Client.Preferences.UI
private void RandomizeName()
{
if (Profile == null) return;
- var name = Profile.Sex.GetName(Profile.Species, _prototypeManager, _random);
+ var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
SetName(name);
UpdateNameEdit();
}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index a654ae5359..ec26a26436 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -58,8 +58,7 @@
-
-
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index 4d1c9130ca..14662679eb 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -61,8 +61,7 @@ namespace Content.Client.Preferences.UI
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
private Button _saveButton => CSaveButton;
- private Button _sexFemaleButton => CSexFemale;
- private Button _sexMaleButton => CSexMale;
+ private OptionButton _sexButton => CSexButton;
private OptionButton _genderButton => CPronounsButton;
private Slider _skinColor => CSkin;
private OptionButton _clothingButton => CClothingButton;
@@ -139,29 +138,10 @@ namespace Content.Client.Preferences.UI
#region Sex
- var sexButtonGroup = new ButtonGroup();
-
- _sexMaleButton.Group = sexButtonGroup;
- _sexMaleButton.OnPressed += args =>
+ _sexButton.OnItemSelected += args =>
{
- SetSex(Sex.Male);
- if (Profile?.Gender == Gender.Female)
- {
- SetGender(Gender.Male);
- UpdateGenderControls();
- }
- };
-
- _sexFemaleButton.Group = sexButtonGroup;
- _sexFemaleButton.OnPressed += _ =>
- {
- SetSex(Sex.Female);
-
- if (Profile?.Gender == Gender.Male)
- {
- SetGender(Gender.Female);
- UpdateGenderControls();
- }
+ _sexButton.SelectId(args.Id);
+ SetSex((Sex) args.Id);
};
#endregion Sex
@@ -779,6 +759,20 @@ namespace Content.Client.Preferences.UI
private void SetSex(Sex newSex)
{
Profile = Profile?.WithSex(newSex);
+ // for convenience, default to most common gender when new sex is selected
+ switch (newSex)
+ {
+ case Sex.Male:
+ Profile = Profile?.WithGender(Gender.Male);
+ break;
+ case Sex.Female:
+ Profile = Profile?.WithGender(Gender.Female);
+ break;
+ default:
+ Profile = Profile?.WithGender(Gender.Epicene);
+ break;
+ }
+ UpdateGenderControls();
IsDirty = true;
}
@@ -793,6 +787,7 @@ namespace Content.Client.Preferences.UI
Profile = Profile?.WithSpecies(newSpecies);
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
+ UpdateSexControls(); // update sex for new species
NeedsDummyRebuild = true;
IsDirty = true;
}
@@ -868,10 +863,35 @@ namespace Content.Client.Preferences.UI
private void UpdateSexControls()
{
- if (Profile?.Sex == Sex.Male)
- _sexMaleButton.Pressed = true;
+ if (Profile == null)
+ return;
+
+ _sexButton.Clear();
+
+ var sexes = new List();
+
+ // add species sex options, default to just none if we are in bizzaro world and have no species
+ if (_prototypeManager.TryIndex(Profile.Species, out var speciesProto))
+ {
+ foreach (var sex in speciesProto.Sexes)
+ {
+ sexes.Add(sex);
+ }
+ } else
+ {
+ sexes.Add(Sex.Unsexed);
+ }
+
+ // add button for each sex
+ foreach (var sex in sexes)
+ {
+ _sexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
+ }
+
+ if (sexes.Contains(Profile.Sex))
+ _sexButton.SelectId((int) Profile.Sex);
else
- _sexFemaleButton.Pressed = true;
+ _sexButton.SelectId((int) sexes[0]);
}
private void UpdateSkinColor()
diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs
index 4228e3feff..e6cb3e382c 100644
--- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs
@@ -2,9 +2,7 @@ using System.Linq;
using Content.Server.Administration.Commands;
using Content.Server.Cargo.Systems;
using Content.Server.Chat.Managers;
-using Content.Server.GameTicking.Rules.Configurations;
using Content.Server.Preferences.Managers;
-using Content.Server.RoundEnd;
using Content.Server.Spawners.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
@@ -19,6 +17,7 @@ using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
+using Robust.Shared.Enums;
namespace Content.Server.GameTicking.Rules;
@@ -37,6 +36,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly PricingSystem _pricingSystem = default!;
+ [Dependency] private readonly NamingSystem _namingSystem = default!;
[ViewVariables]
private List _pirates = new();
@@ -198,8 +198,9 @@ public sealed class PiratesRuleSystem : GameRuleSystem
for (var i = 0; i < ops.Length; i++)
{
var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female;
+ var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
- var name = sex.GetName("Human", _prototypeManager, _random);
+ var name = _namingSystem.GetName("Human", gender);
var session = ops[i];
var newMind = new Mind.Mind(session.UserId)
diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs
index 009801046c..42e12944d5 100644
--- a/Content.Server/IdentityManagement/IdentitySystem.cs
+++ b/Content.Server/IdentityManagement/IdentitySystem.cs
@@ -122,7 +122,7 @@ public class IdentitySystem : SharedIdentitySystem
HumanoidComponent? appearance=null)
{
int age = HumanoidCharacterProfile.MinimumAge;
- Gender gender = Gender.Neuter;
+ Gender gender = Gender.Epicene;
// Always use their actual age and gender, since that can't really be changed by an ID.
if (Resolve(target, ref appearance, false))
diff --git a/Content.Server/Speech/Components/VocalComponent.cs b/Content.Server/Speech/Components/VocalComponent.cs
index 5f9cd73644..d30b29f055 100644
--- a/Content.Server/Speech/Components/VocalComponent.cs
+++ b/Content.Server/Speech/Components/VocalComponent.cs
@@ -17,6 +17,9 @@ public sealed class VocalComponent : Component
[DataField("femaleScream")]
public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams");
+ [DataField("unsexedScream")]
+ public SoundSpecifier UnsexedScream = new SoundCollectionSpecifier("MaleScreams");
+
[DataField("wilhelm")]
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");
diff --git a/Content.Server/Speech/VocalSystem.cs b/Content.Server/Speech/VocalSystem.cs
index bb081d7b7d..f00f499eb1 100644
--- a/Content.Server/Speech/VocalSystem.cs
+++ b/Content.Server/Speech/VocalSystem.cs
@@ -67,9 +67,7 @@ public sealed class VocalSystem : EntitySystem
if (!_blocker.CanSpeak(uid))
return false;
- var sex = Sex.Male; //the default is male because requiring humanoid appearance for this is dogshit
- if (TryComp(uid, out HumanoidComponent? humanoid))
- sex = humanoid.Sex;
+ var sex = CompOrNull(uid)?.Sex ?? Sex.Unsexed;
if (_random.Prob(component.WilhelmProbability))
{
@@ -89,7 +87,8 @@ public sealed class VocalSystem : EntitySystem
SoundSystem.Play(component.FemaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
break;
default:
- throw new ArgumentOutOfRangeException();
+ SoundSystem.Play(component.UnsexedScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
+ break;
}
return true;
diff --git a/Content.Shared/Humanoid/NamingSystem.cs b/Content.Shared/Humanoid/NamingSystem.cs
new file mode 100644
index 0000000000..d19667898a
--- /dev/null
+++ b/Content.Shared/Humanoid/NamingSystem.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Dataset;
+using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Enums;
+
+namespace Content.Shared.Humanoid
+{
+ ///
+ /// Figure out how to name a humanoid with these extensions.
+ ///
+ public sealed class NamingSystem : EntitySystem
+ {
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ public string GetName(string species, Gender? gender = null)
+ {
+ // if they have an old species or whatever just fall back to human I guess?
+ // Some downstream is probably gonna have this eventually but then they can deal with fallbacks.
+ if (!_prototypeManager.TryIndex(species, out SpeciesPrototype? speciesProto))
+ {
+ speciesProto = _prototypeManager.Index("Human");
+ Logger.Warning($"Unable to find species {species} for name, falling back to Human");
+ }
+
+ switch (speciesProto.Naming)
+ {
+ case SpeciesNaming.FirstDashFirst:
+ return $"{GetFirstName(speciesProto, gender)}-{GetFirstName(speciesProto, gender)}";
+ case SpeciesNaming.FirstLast:
+ default:
+ return $"{GetFirstName(speciesProto, gender)} {GetLastName(speciesProto)}";
+ }
+ }
+
+ public string GetFirstName(SpeciesPrototype speciesProto, Gender? gender = null)
+ {
+ switch (gender)
+ {
+ case Gender.Male:
+ return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames).Values);
+ case Gender.Female:
+ return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames).Values);
+ default:
+ if (_random.Prob(0.5f))
+ return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames).Values);
+ else
+ return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames).Values);
+ }
+ }
+
+ public string GetLastName(SpeciesPrototype speciesProto)
+ {
+ return _random.Pick(_prototypeManager.Index(speciesProto.LastNames).Values);
+ }
+ }
+}
diff --git a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
index cf1f3ebb3f..7b36f9c30e 100644
--- a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
+++ b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs
@@ -92,6 +92,9 @@ public sealed class SpeciesPrototype : IPrototype
[DataField("naming")]
public SpeciesNaming Naming { get; } = SpeciesNaming.FirstLast;
+
+ [DataField("sexes")]
+ public List Sexes { get; } = new List(){ Sex.Male, Sex.Female };
}
public enum SpeciesNaming : byte
diff --git a/Content.Shared/Humanoid/Sex.cs b/Content.Shared/Humanoid/Sex.cs
index b99e97db81..aeb85bdb54 100644
--- a/Content.Shared/Humanoid/Sex.cs
+++ b/Content.Shared/Humanoid/Sex.cs
@@ -5,58 +5,11 @@ using Robust.Shared.Random;
namespace Content.Shared.Humanoid
{
+ // You need to update profile, profile editor, maybe voices and names if you want to expand this further.
public enum Sex : byte
{
Male,
- Female
- }
-
- public static class SexExtensions
- {
- public static string GetName(this Sex sex, string species, IPrototypeManager? prototypeManager = null, IRobustRandom? random = null)
- {
- IoCManager.Resolve(ref prototypeManager);
- IoCManager.Resolve(ref random);
-
- // if they have an old species or whatever just fall back to human I guess?
- // Some downstream is probably gonna have this eventually but then they can deal with fallbacks.
- if (!prototypeManager.TryIndex(species, out SpeciesPrototype? speciesProto))
- {
- speciesProto = prototypeManager.Index("Human");
- Logger.Warning($"Unable to find species {species} for name, falling back to Human");
- }
-
- switch (speciesProto.Naming)
- {
- case SpeciesNaming.FirstDashFirst:
- return $"{GetFirstName(sex, speciesProto, prototypeManager, random)}-{GetFirstName(sex, speciesProto, prototypeManager, random)}";
- case SpeciesNaming.FirstLast:
- default:
- return $"{GetFirstName(sex, speciesProto, prototypeManager, random)} {GetLastName(sex, speciesProto, prototypeManager, random)}";
- }
- }
-
- public static string GetFirstName(this Sex sex, SpeciesPrototype speciesProto, IPrototypeManager? protoManager = null, IRobustRandom? random = null)
- {
- IoCManager.Resolve(ref protoManager);
- IoCManager.Resolve(ref random);
-
- switch (sex)
- {
- case Sex.Male:
- return random.Pick(protoManager.Index(speciesProto.MaleFirstNames).Values);
- case Sex.Female:
- return random.Pick(protoManager.Index(speciesProto.FemaleFirstNames).Values);
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-
- public static string GetLastName(this Sex sex, SpeciesPrototype speciesProto, IPrototypeManager? protoManager = null, IRobustRandom? random = null)
- {
- IoCManager.Resolve(ref protoManager);
- IoCManager.Resolve(ref random);
- return random.Pick(protoManager.Index(speciesProto.LastNames).Values);
- }
+ Female,
+ Unsexed,
}
}
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index 14af731c6a..157b464385 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -171,10 +171,15 @@ namespace Content.Shared.Preferences
var prototypeManager = IoCManager.Resolve();
var random = IoCManager.Resolve();
- var sex = random.Prob(0.5f) ? Sex.Male : Sex.Female;
+ var sex = Sex.Unsexed;
+ if (prototypeManager.TryIndex(species, out var speciesPrototype))
+ {
+ sex = random.Pick(speciesPrototype.Sexes);
+ }
+
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
- var name = sex.GetName(species, prototypeManager, random);
+ var name = GetName(species, gender);
var age = random.Next(MinimumAge, MaximumAge);
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack,
@@ -351,13 +356,27 @@ namespace Content.Shared.Preferences
{
var age = Math.Clamp(Age, MinimumAge, MaximumAge);
+ var prototypeManager = IoCManager.Resolve();
+
+ prototypeManager.TryIndex(Species, out var speciesPrototype);
+
var sex = Sex switch
{
Sex.Male => Sex.Male,
Sex.Female => Sex.Female,
+ Sex.Unsexed => Sex.Unsexed,
_ => Sex.Male // Invalid enum values.
};
+ // ensure the species can be that sex
+ if (speciesPrototype != null)
+ {
+ if (!speciesPrototype.Sexes.Contains(sex))
+ {
+ sex = speciesPrototype.Sexes[0];
+ }
+ }
+
var gender = Gender switch
{
Gender.Epicene => Gender.Epicene,
@@ -370,7 +389,7 @@ namespace Content.Shared.Preferences
string name;
if (string.IsNullOrEmpty(Name))
{
- name = Sex.GetName(Species);
+ name = GetName(Species, gender);
}
else if (Name.Length > MaxNameLength)
{
@@ -399,7 +418,7 @@ namespace Content.Shared.Preferences
if (string.IsNullOrEmpty(name))
{
- name = Sex.GetName(Species);
+ name = GetName(Species, gender);
}
string flavortext;
@@ -436,8 +455,6 @@ namespace Content.Shared.Preferences
_ => BackpackPreference.Backpack // Invalid enum values.
};
- var prototypeManager = IoCManager.Resolve();
-
var priorities = new Dictionary(JobPriorities
.Where(p => prototypeManager.HasIndex(p.Key) && p.Value switch
{
@@ -481,6 +498,14 @@ namespace Content.Shared.Preferences
_traitPreferences.AddRange(traits);
}
+ // sorry this is kind of weird and duplicated,
+ /// working inside these non entity systems is a bit wack
+ public static string GetName(string species, Gender gender)
+ {
+ var namingSystem = IoCManager.Resolve().GetEntitySystem();
+ return namingSystem.GetName(species, gender);
+ }
+
public override bool Equals(object? obj)
{
return obj is HumanoidCharacterProfile other && MemberwiseEquals(other);
diff --git a/Content.Shared/StationRecords/GeneralStationRecord.cs b/Content.Shared/StationRecords/GeneralStationRecord.cs
index 94bc98913a..b1e2a6065a 100644
--- a/Content.Shared/StationRecords/GeneralStationRecord.cs
+++ b/Content.Shared/StationRecords/GeneralStationRecord.cs
@@ -47,7 +47,7 @@ public sealed class GeneralStationRecord
///
/// Sex should be placed in a medical record, not a general record.
[ViewVariables]
- public Gender Gender = Gender.Neuter;
+ public Gender Gender = Gender.Epicene;
///
/// The priority to display this record at.
diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
index aff4a5de96..4b7dccd2bc 100644
--- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
+++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
@@ -3,8 +3,9 @@ humanoid-profile-editor-name-label = Name:
humanoid-profile-editor-name-random-button = Randomize
humanoid-profile-editor-appearance-tab = Appearance
humanoid-profile-editor-sex-label = Sex:
-humanoid-profile-editor-sex-male-button = Male
-humanoid-profile-editor-sex-female-button = Female
+humanoid-profile-editor-sex-male-text = Male
+humanoid-profile-editor-sex-female-text = Female
+humanoid-profile-editor-sex-unsexed-text = None
humanoid-profile-editor-age-label = Age:
humanoid-profile-editor-skin-color-label = Skin color:
humanoid-profile-editor-species-label = Species:
diff --git a/Resources/Prototypes/Species/slime.yml b/Resources/Prototypes/Species/slime.yml
index 5879ce6a64..32a6b448ed 100644
--- a/Resources/Prototypes/Species/slime.yml
+++ b/Resources/Prototypes/Species/slime.yml
@@ -8,6 +8,8 @@
markingLimits: MobSlimeMarkingLimits
dollPrototype: MobSlimePersonDummy
skinColoration: Hues
+ sexes:
+ - Unsexed
- type: speciesBaseSprites
id: MobSlimeSprites