From 9805fe39564ae1f64bb479f96c00864d4e73fb4c Mon Sep 17 00:00:00 2001 From: Falcon Date: Tue, 30 Jun 2026 20:46:27 -0700 Subject: [PATCH] Refactor voice to separate it from sex (#40593) * feat: separate sex from voice * wip: tried to make it use voice collection directly * fix: yaml * fix: migrations * fix: ensure valid now validates correctly * fix: unisex mobs wouldn't have voice * chore: fix RT version * refactor: clean up code into subfunctions * refactor: swap name and protoId in voice also rename sounds to voice * chore: add comment * fix: fallback sounds for non-humanoid characters * fix: tests * fix: tests * refactor: change requests * refactor: convert sheepers * refactor: move Voice to SpeciesPrototype * chore: add tests * fix: test IoC error * refactor: use null coalesing in ConvertProfiles * refactor: move vocal sex association to speciesPrototype * style: remove space from new() * refactor: use name from emoteSoundsPrototype for UI * refactor: no more null Voice on profile * refactor: make DefaultVoice a static readonly property * fix: add default voice to HumanoidProfileComponent * chore: remove repeated vocal component from species yaml It's inherited from BaseSpeciesMob * style: correct comments for refactor * fix: add missing locale for FemaleVulpkanin * refactor: simplify logic based off surounding code * test: add test to make sure that sexes are properly enumerated for the voices * fix: add back urist sounds * style: clarify comments again * style: last thing im changing before I sleep * refactor: battling programming semantics * refactor: Name on EmoteSoundsPrototype.cs to VoiceSelectorName for clarity * revert: isDirty change * Fix HumanoidProfileTest.EnsureValidLoading. The profile's voice wasnt being updated, thus the voiceChanged event was never raised and that meant that the voice component had old EmoteSounds * oops * refactor: do the easy change requests * refactor: use an array for indexing sex conversion for performance * fix master * mistakes were made * chore: redo migrations --------- Co-authored-by: AJCM-git --- .../UI/HumanoidProfileEditor.Appearance.cs | 43 + .../Lobby/UI/HumanoidProfileEditor.xaml | 6 + .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 11 + .../Tests/Humanoid/HumanoidProfileTests.cs | 47 +- .../20260701024227_VoiceSelector.Designer.cs | 2239 +++++++++++++++++ .../Postgres/20260701024227_VoiceSelector.cs | 28 + .../PostgresServerDbContextModelSnapshot.cs | 6 +- .../20260701024222_VoiceSelector.Designer.cs | 2156 ++++++++++++++++ .../Sqlite/20260701024222_VoiceSelector.cs | 28 + .../SqliteServerDbContextModelSnapshot.cs | 6 +- Content.Server.Database/Model.cs | 1 + Content.Server/Database/ServerDbBase.cs | 1 + .../Managers/ServerPreferencesManager.cs | 10 +- .../Speech/EntitySystems/VocalSystem.cs | 21 +- .../Chat/Prototypes/EmoteSoundsPrototype.cs | 6 + .../Humanoid/HumanoidProfileComponent.cs | 10 + .../Humanoid/HumanoidProfileExportV1.cs | 12 +- .../Humanoid/HumanoidProfileSystem.cs | 5 +- .../Humanoid/Prototypes/SpeciesPrototype.cs | 13 + Content.Shared/Humanoid/Sex.cs | 14 +- Content.Shared/Humanoid/Voice.cs | 11 + .../Preferences/HumanoidCharacterProfile.cs | 24 + .../Speech/Components/VocalComponent.cs | 13 +- .../ui/humanoid-profile-editor.ftl | 7 + .../Prototypes/Body/Species/arachnid.yml | 5 +- Resources/Prototypes/Body/Species/diona.yml | 5 +- Resources/Prototypes/Body/Species/dwarf.yml | 5 +- Resources/Prototypes/Body/Species/moth.yml | 5 +- .../Prototypes/Body/Species/reptilian.yml | 5 +- .../Prototypes/Body/Species/skeleton.yml | 5 +- Resources/Prototypes/Body/Species/slime.yml | 5 +- Resources/Prototypes/Body/Species/vox.yml | 5 +- .../Prototypes/Body/Species/vulpkanin.yml | 5 +- Resources/Prototypes/Body/species_base.yml | 5 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 12 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 41 +- .../Entities/Mobs/NPCs/regalrat.yml | 5 +- .../Prototypes/Entities/Mobs/NPCs/scurret.yml | 5 +- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 6 +- .../Prototypes/Entities/Mobs/NPCs/space.yml | 16 +- .../Entities/Mobs/Player/familiars.yml | 5 +- .../Entities/Mobs/Player/mothershipcore.yml | 3 +- .../Prototypes/Entities/Objects/Fun/pai.yml | 6 +- Resources/Prototypes/Species/arachnid.yml | 6 + Resources/Prototypes/Species/diona.yml | 6 + Resources/Prototypes/Species/dwarf.yml | 7 + Resources/Prototypes/Species/moth.yml | 6 + Resources/Prototypes/Species/reptilian.yml | 7 + Resources/Prototypes/Species/slime.yml | 7 + Resources/Prototypes/Species/vox.yml | 6 + Resources/Prototypes/Species/vulpkanin.yml | 7 + .../Prototypes/Voice/speech_emote_sounds.yml | 14 + 52 files changed, 4773 insertions(+), 160 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20260701024227_VoiceSelector.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20260701024227_VoiceSelector.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20260701024222_VoiceSelector.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20260701024222_VoiceSelector.cs create mode 100644 Content.Shared/Humanoid/Voice.cs diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs index ddc9752b1e..c61e3fb678 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs @@ -1,9 +1,11 @@ using System.Linq; using Content.Client.UserInterface.Systems.Guidebook; +using Content.Shared.Chat.Prototypes; using Content.Shared.Guidebook; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; +using Content.Shared.Speech.Components; using Robust.Client.UserInterface.Controls; using Robust.Shared.Enums; using Robust.Shared.Prototypes; @@ -16,6 +18,7 @@ public sealed partial class HumanoidProfileEditor private ColorSelectorSliders _rgbSkinColorSelector; private List _species = new(); + private List _voices = new(); private static readonly ProtoId DefaultSpeciesGuidebook = "Species"; public void UpdateSpeciesGuidebookIcon() @@ -97,6 +100,34 @@ public sealed partial class HumanoidProfileEditor EyeColorPicker.SetData(Profile.Appearance.EyeColor); } + private void UpdateVoiceControls() + { + if (Profile == null) + return; + + VoiceButton.Clear(); + _voices.Clear(); + + var speciesPrototype = _prototypeManager.Index(Profile.Species); + var availableVoices = speciesPrototype.Voices; + + _voices.AddRange(availableVoices.Select(protoId => _prototypeManager.Index(protoId))); + + if (_voices.All(proto => Profile?.Voice != proto.ID)) + SetVoice(speciesPrototype.DefaultSoundsBySex[(int)Profile.Sex]); + + for (var i = 0; i < availableVoices.Count; i++) + { + var name = Loc.GetString(_voices[i].VoiceSelectorName); + VoiceButton.AddItem(name, i); + + if (Profile?.Voice.Equals(_voices[i].ID) == true) + { + VoiceButton.SelectId(i); + } + } + } + private void UpdateSkinColor() { if (Profile == null) @@ -188,6 +219,7 @@ public sealed partial class HumanoidProfileEditor // In case there's species restrictions for loadouts RefreshLoadouts(); UpdateSexControls(); // update sex for new species + UpdateVoiceControls(); UpdateSpeciesGuidebookIcon(); ReloadPreview(); } @@ -215,11 +247,22 @@ public sealed partial class HumanoidProfileEditor break; } + // this does the same as above but for voice + if (_prototypeManager.TryIndex(Profile?.Species, out var prototype)) + SetVoice(prototype.DefaultSoundsBySex[(int)newSex]); + UpdateGenderControls(); + UpdateVoiceControls(); _markingsModel.SetOrganSexes(newSex); ReloadPreview(); } + private void SetVoice(ProtoId newVoice) + { + Profile = Profile?.WithVoice(newVoice); + IsDirty = true; + } + private void SetGender(Gender newGender) { Profile = Profile?.WithGender(newGender); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index 84c5b75d1c..cce8ad6296 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -74,6 +74,12 @@ + + +