Let species prototypes define valid sexes (Sex Refactor) (#11520)

This commit is contained in:
Rane
2022-10-15 17:45:47 -04:00
committed by GitHub
parent 0e18ba567c
commit c70e423ff6
14 changed files with 161 additions and 97 deletions

View File

@@ -22,7 +22,7 @@ namespace Content.Client.Preferences.UI
private void RandomizeName() private void RandomizeName()
{ {
if (Profile == null) return; if (Profile == null) return;
var name = Profile.Sex.GetName(Profile.Species, _prototypeManager, _random); var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
SetName(name); SetName(name);
UpdateNameEdit(); UpdateNameEdit();
} }

View File

@@ -58,8 +58,7 @@
<prefUi:HighlightedContainer> <prefUi:HighlightedContainer>
<BoxContainer Orientation="Horizontal"> <BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'humanoid-profile-editor-sex-label'}" /> <Label Text="{Loc 'humanoid-profile-editor-sex-label'}" />
<Button Name="CSexMale" Text="{Loc 'humanoid-profile-editor-sex-male-button'}" /> <OptionButton Name="CSexButton" />
<Button Name="CSexFemale" Text="{Loc 'humanoid-profile-editor-sex-female-button'}" />
</BoxContainer> </BoxContainer>
</prefUi:HighlightedContainer> </prefUi:HighlightedContainer>
<!-- Age --> <!-- Age -->

View File

@@ -61,8 +61,7 @@ namespace Content.Client.Preferences.UI
private Button _randomizeEverythingButton => CRandomizeEverything; private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel; private RichTextLabel _warningLabel => CWarningLabel;
private Button _saveButton => CSaveButton; private Button _saveButton => CSaveButton;
private Button _sexFemaleButton => CSexFemale; private OptionButton _sexButton => CSexButton;
private Button _sexMaleButton => CSexMale;
private OptionButton _genderButton => CPronounsButton; private OptionButton _genderButton => CPronounsButton;
private Slider _skinColor => CSkin; private Slider _skinColor => CSkin;
private OptionButton _clothingButton => CClothingButton; private OptionButton _clothingButton => CClothingButton;
@@ -139,29 +138,10 @@ namespace Content.Client.Preferences.UI
#region Sex #region Sex
var sexButtonGroup = new ButtonGroup(); _sexButton.OnItemSelected += args =>
_sexMaleButton.Group = sexButtonGroup;
_sexMaleButton.OnPressed += args =>
{ {
SetSex(Sex.Male); _sexButton.SelectId(args.Id);
if (Profile?.Gender == Gender.Female) SetSex((Sex) args.Id);
{
SetGender(Gender.Male);
UpdateGenderControls();
}
};
_sexFemaleButton.Group = sexButtonGroup;
_sexFemaleButton.OnPressed += _ =>
{
SetSex(Sex.Female);
if (Profile?.Gender == Gender.Male)
{
SetGender(Gender.Female);
UpdateGenderControls();
}
}; };
#endregion Sex #endregion Sex
@@ -779,6 +759,20 @@ namespace Content.Client.Preferences.UI
private void SetSex(Sex newSex) private void SetSex(Sex newSex)
{ {
Profile = Profile?.WithSex(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; IsDirty = true;
} }
@@ -793,6 +787,7 @@ namespace Content.Client.Preferences.UI
Profile = Profile?.WithSpecies(newSpecies); Profile = Profile?.WithSpecies(newSpecies);
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it. OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well. CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
UpdateSexControls(); // update sex for new species
NeedsDummyRebuild = true; NeedsDummyRebuild = true;
IsDirty = true; IsDirty = true;
} }
@@ -868,10 +863,35 @@ namespace Content.Client.Preferences.UI
private void UpdateSexControls() private void UpdateSexControls()
{ {
if (Profile?.Sex == Sex.Male) if (Profile == null)
_sexMaleButton.Pressed = true; return;
_sexButton.Clear();
var sexes = new List<Sex>();
// add species sex options, default to just none if we are in bizzaro world and have no species
if (_prototypeManager.TryIndex<SpeciesPrototype>(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 else
_sexFemaleButton.Pressed = true; _sexButton.SelectId((int) sexes[0]);
} }
private void UpdateSkinColor() private void UpdateSkinColor()

View File

@@ -2,9 +2,7 @@ using System.Linq;
using Content.Server.Administration.Commands; using Content.Server.Administration.Commands;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Configurations;
using Content.Server.Preferences.Managers; using Content.Server.Preferences.Managers;
using Content.Server.RoundEnd;
using Content.Server.Spawners.Components; using Content.Server.Spawners.Components;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
@@ -19,6 +17,7 @@ using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Content.Server.GameTicking.Rules; namespace Content.Server.GameTicking.Rules;
@@ -37,6 +36,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!; [Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly PricingSystem _pricingSystem = default!; [Dependency] private readonly PricingSystem _pricingSystem = default!;
[Dependency] private readonly NamingSystem _namingSystem = default!;
[ViewVariables] [ViewVariables]
private List<Mind.Mind> _pirates = new(); private List<Mind.Mind> _pirates = new();
@@ -198,8 +198,9 @@ public sealed class PiratesRuleSystem : GameRuleSystem
for (var i = 0; i < ops.Length; i++) for (var i = 0; i < ops.Length; i++)
{ {
var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female; 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 session = ops[i];
var newMind = new Mind.Mind(session.UserId) var newMind = new Mind.Mind(session.UserId)

View File

@@ -122,7 +122,7 @@ public class IdentitySystem : SharedIdentitySystem
HumanoidComponent? appearance=null) HumanoidComponent? appearance=null)
{ {
int age = HumanoidCharacterProfile.MinimumAge; 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. // Always use their actual age and gender, since that can't really be changed by an ID.
if (Resolve(target, ref appearance, false)) if (Resolve(target, ref appearance, false))

View File

@@ -17,6 +17,9 @@ public sealed class VocalComponent : Component
[DataField("femaleScream")] [DataField("femaleScream")]
public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams"); public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams");
[DataField("unsexedScream")]
public SoundSpecifier UnsexedScream = new SoundCollectionSpecifier("MaleScreams");
[DataField("wilhelm")] [DataField("wilhelm")]
public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg"); public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg");

View File

@@ -67,9 +67,7 @@ public sealed class VocalSystem : EntitySystem
if (!_blocker.CanSpeak(uid)) if (!_blocker.CanSpeak(uid))
return false; return false;
var sex = Sex.Male; //the default is male because requiring humanoid appearance for this is dogshit var sex = CompOrNull<HumanoidComponent>(uid)?.Sex ?? Sex.Unsexed;
if (TryComp(uid, out HumanoidComponent? humanoid))
sex = humanoid.Sex;
if (_random.Prob(component.WilhelmProbability)) if (_random.Prob(component.WilhelmProbability))
{ {
@@ -89,7 +87,8 @@ public sealed class VocalSystem : EntitySystem
SoundSystem.Play(component.FemaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams); SoundSystem.Play(component.FemaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); SoundSystem.Play(component.UnsexedScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams);
break;
} }
return true; return true;

View File

@@ -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
{
/// <summary>
/// Figure out how to name a humanoid with these extensions.
/// </summary>
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<SpeciesPrototype>("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<DatasetPrototype>(speciesProto.MaleFirstNames).Values);
case Gender.Female:
return _random.Pick(_prototypeManager.Index<DatasetPrototype>(speciesProto.FemaleFirstNames).Values);
default:
if (_random.Prob(0.5f))
return _random.Pick(_prototypeManager.Index<DatasetPrototype>(speciesProto.MaleFirstNames).Values);
else
return _random.Pick(_prototypeManager.Index<DatasetPrototype>(speciesProto.FemaleFirstNames).Values);
}
}
public string GetLastName(SpeciesPrototype speciesProto)
{
return _random.Pick(_prototypeManager.Index<DatasetPrototype>(speciesProto.LastNames).Values);
}
}
}

View File

@@ -92,6 +92,9 @@ public sealed class SpeciesPrototype : IPrototype
[DataField("naming")] [DataField("naming")]
public SpeciesNaming Naming { get; } = SpeciesNaming.FirstLast; public SpeciesNaming Naming { get; } = SpeciesNaming.FirstLast;
[DataField("sexes")]
public List<Sex> Sexes { get; } = new List<Sex>(){ Sex.Male, Sex.Female };
} }
public enum SpeciesNaming : byte public enum SpeciesNaming : byte

View File

@@ -5,58 +5,11 @@ using Robust.Shared.Random;
namespace Content.Shared.Humanoid 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 public enum Sex : byte
{ {
Male, Male,
Female Female,
} Unsexed,
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<SpeciesPrototype>("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<DatasetPrototype>(speciesProto.MaleFirstNames).Values);
case Sex.Female:
return random.Pick(protoManager.Index<DatasetPrototype>(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<DatasetPrototype>(speciesProto.LastNames).Values);
}
} }
} }

View File

@@ -171,10 +171,15 @@ namespace Content.Shared.Preferences
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var random = IoCManager.Resolve<IRobustRandom>(); var random = IoCManager.Resolve<IRobustRandom>();
var sex = random.Prob(0.5f) ? Sex.Male : Sex.Female; var sex = Sex.Unsexed;
if (prototypeManager.TryIndex<SpeciesPrototype>(species, out var speciesPrototype))
{
sex = random.Pick(speciesPrototype.Sexes);
}
var gender = sex == Sex.Male ? Gender.Male : Gender.Female; 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); var age = random.Next(MinimumAge, MaximumAge);
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, 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 age = Math.Clamp(Age, MinimumAge, MaximumAge);
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.TryIndex<SpeciesPrototype>(Species, out var speciesPrototype);
var sex = Sex switch var sex = Sex switch
{ {
Sex.Male => Sex.Male, Sex.Male => Sex.Male,
Sex.Female => Sex.Female, Sex.Female => Sex.Female,
Sex.Unsexed => Sex.Unsexed,
_ => Sex.Male // Invalid enum values. _ => 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 var gender = Gender switch
{ {
Gender.Epicene => Gender.Epicene, Gender.Epicene => Gender.Epicene,
@@ -370,7 +389,7 @@ namespace Content.Shared.Preferences
string name; string name;
if (string.IsNullOrEmpty(Name)) if (string.IsNullOrEmpty(Name))
{ {
name = Sex.GetName(Species); name = GetName(Species, gender);
} }
else if (Name.Length > MaxNameLength) else if (Name.Length > MaxNameLength)
{ {
@@ -399,7 +418,7 @@ namespace Content.Shared.Preferences
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
name = Sex.GetName(Species); name = GetName(Species, gender);
} }
string flavortext; string flavortext;
@@ -436,8 +455,6 @@ namespace Content.Shared.Preferences
_ => BackpackPreference.Backpack // Invalid enum values. _ => BackpackPreference.Backpack // Invalid enum values.
}; };
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var priorities = new Dictionary<string, JobPriority>(JobPriorities var priorities = new Dictionary<string, JobPriority>(JobPriorities
.Where(p => prototypeManager.HasIndex<JobPrototype>(p.Key) && p.Value switch .Where(p => prototypeManager.HasIndex<JobPrototype>(p.Key) && p.Value switch
{ {
@@ -481,6 +498,14 @@ namespace Content.Shared.Preferences
_traitPreferences.AddRange(traits); _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<IEntitySystemManager>().GetEntitySystem<NamingSystem>();
return namingSystem.GetName(species, gender);
}
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
return obj is HumanoidCharacterProfile other && MemberwiseEquals(other); return obj is HumanoidCharacterProfile other && MemberwiseEquals(other);

View File

@@ -47,7 +47,7 @@ public sealed class GeneralStationRecord
/// </summary> /// </summary>
/// <remarks>Sex should be placed in a medical record, not a general record.</remarks> /// <remarks>Sex should be placed in a medical record, not a general record.</remarks>
[ViewVariables] [ViewVariables]
public Gender Gender = Gender.Neuter; public Gender Gender = Gender.Epicene;
/// <summary> /// <summary>
/// The priority to display this record at. /// The priority to display this record at.

View File

@@ -3,8 +3,9 @@ humanoid-profile-editor-name-label = Name:
humanoid-profile-editor-name-random-button = Randomize humanoid-profile-editor-name-random-button = Randomize
humanoid-profile-editor-appearance-tab = Appearance humanoid-profile-editor-appearance-tab = Appearance
humanoid-profile-editor-sex-label = Sex: humanoid-profile-editor-sex-label = Sex:
humanoid-profile-editor-sex-male-button = Male humanoid-profile-editor-sex-male-text = Male
humanoid-profile-editor-sex-female-button = Female humanoid-profile-editor-sex-female-text = Female
humanoid-profile-editor-sex-unsexed-text = None
humanoid-profile-editor-age-label = Age: humanoid-profile-editor-age-label = Age:
humanoid-profile-editor-skin-color-label = Skin color: humanoid-profile-editor-skin-color-label = Skin color:
humanoid-profile-editor-species-label = Species: humanoid-profile-editor-species-label = Species:

View File

@@ -8,6 +8,8 @@
markingLimits: MobSlimeMarkingLimits markingLimits: MobSlimeMarkingLimits
dollPrototype: MobSlimePersonDummy dollPrototype: MobSlimePersonDummy
skinColoration: Hues skinColoration: Hues
sexes:
- Unsexed
- type: speciesBaseSprites - type: speciesBaseSprites
id: MobSlimeSprites id: MobSlimeSprites