using System.Linq; using System.Numerics; using Content.Client.Guidebook; using Content.Client.Humanoid; using Content.Client.Lobby; using Content.Client.Message; using Content.Client.Players.PlayTimeTracking; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Systems.Guidebook; using Content.Shared.CCVar; using Content.Shared.Clothing; using Content.Shared.GameTicking; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Roles; using Content.Shared.Traits; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Client.Utility; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.Preferences.UI { [GenerateTypedNameReferences] public sealed partial class HumanoidProfileEditor : BoxContainer { private readonly IClientPreferencesManager _preferencesManager; private readonly IPrototypeManager _prototypeManager; private readonly MarkingManager _markingManager; private readonly JobRequirementsManager _requirements; private LineEdit _ageEdit => CAgeEdit; private LineEdit _nameEdit => CNameEdit; private TextEdit? _flavorTextEdit; private Button _nameRandomButton => CNameRandomize; private Button _randomizeEverythingButton => CRandomizeEverything; private RichTextLabel _warningLabel => CWarningLabel; private Button _saveButton => CSaveButton; private OptionButton _sexButton => CSexButton; private OptionButton _genderButton => CPronounsButton; private Slider _skinColor => CSkin; private OptionButton _spawnPriorityButton => CSpawnPriorityButton; private SingleMarkingPicker _hairPicker => CHairStylePicker; private SingleMarkingPicker _facialHairPicker => CFacialHairPicker; private EyeColorPicker _eyesPicker => CEyeColorPicker; private TabContainer _tabContainer => CTabContainer; private BoxContainer _jobList => CJobList; private BoxContainer _antagList => CAntagList; private BoxContainer _traitsList => CTraitsList; private readonly List _jobPriorities; private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton; private readonly Dictionary _jobCategories; // Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony private readonly List _speciesList; private readonly List _antagPreferences = new(); private readonly List _traitPreferences; private SpriteView _previewSpriteView => CSpriteView; private Button _previewRotateLeftButton => CSpriteRotateLeft; private Button _previewRotateRightButton => CSpriteRotateRight; private Direction _previewRotation = Direction.North; private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer; private ColorSelectorSliders _rgbSkinColorSelector; private bool _isDirty; public int CharacterSlot; public HumanoidCharacterProfile? Profile; public event Action? OnProfileChanged; [ValidatePrototypeId] private const string DefaultSpeciesGuidebook = "Species"; public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager) { RobustXamlLoader.Load(this); _prototypeManager = prototypeManager; _preferencesManager = preferencesManager; _markingManager = IoCManager.Resolve(); var controller = UserInterfaceManager.GetUIController(); controller.PreviewDummyUpdated += OnDummyUpdate; _previewSpriteView.SetEntity(controller.GetPreviewDummy()); #region Left #region Name _nameEdit.OnTextChanged += args => { SetName(args.Text); }; _nameRandomButton.OnPressed += args => RandomizeName(); _randomizeEverythingButton.OnPressed += args => { RandomizeEverything(); }; _warningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]"); #endregion Name #region Appearance _tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab")); #region Sex _sexButton.OnItemSelected += args => { _sexButton.SelectId(args.Id); SetSex((Sex) args.Id); }; #endregion Sex #region Age _ageEdit.OnTextChanged += args => { if (!int.TryParse(args.Text, out var newAge)) return; SetAge(newAge); }; #endregion Age #region Gender _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male); _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female); _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene); _genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter); _genderButton.OnItemSelected += args => { _genderButton.SelectId(args.Id); SetGender((Gender) args.Id); }; #endregion Gender #region Species _speciesList = prototypeManager.EnumeratePrototypes().Where(o => o.RoundStart).ToList(); for (var i = 0; i < _speciesList.Count; i++) { var name = Loc.GetString(_speciesList[i].Name); CSpeciesButton.AddItem(name, i); } CSpeciesButton.OnItemSelected += args => { CSpeciesButton.SelectId(args.Id); SetSpecies(_speciesList[args.Id].ID); UpdateHairPickers(); OnSkinColorOnValueChanged(); }; #endregion Species #region Skin _skinColor.OnValueChanged += _ => { OnSkinColorOnValueChanged(); }; _rgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders()); _rgbSkinColorSelector.OnColorChanged += _ => { OnSkinColorOnValueChanged(); }; #endregion #region Hair _hairPicker.OnMarkingSelect += newStyle => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithHairStyleName(newStyle.id)); SetDirty(); }; _hairPicker.OnColorChanged += newColor => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0])); UpdateCMarkingsHair(); SetDirty(); }; _facialHairPicker.OnMarkingSelect += newStyle => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithFacialHairStyleName(newStyle.id)); SetDirty(); }; _facialHairPicker.OnColorChanged += newColor => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0])); UpdateCMarkingsFacialHair(); SetDirty(); }; _hairPicker.OnSlotRemove += _ => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle) ); UpdateHairPickers(); UpdateCMarkingsHair(); SetDirty(); }; _facialHairPicker.OnSlotRemove += _ => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle) ); UpdateHairPickers(); UpdateCMarkingsFacialHair(); SetDirty(); }; _hairPicker.OnSlotAdd += delegate() { if (Profile is null) return; var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, Profile.Species).Keys .FirstOrDefault(); if (string.IsNullOrEmpty(hair)) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithHairStyleName(hair) ); UpdateHairPickers(); UpdateCMarkingsHair(); SetDirty(); }; _facialHairPicker.OnSlotAdd += delegate() { if (Profile is null) return; var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, Profile.Species).Keys .FirstOrDefault(); if (string.IsNullOrEmpty(hair)) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithFacialHairStyleName(hair) ); UpdateHairPickers(); UpdateCMarkingsFacialHair(); SetDirty(); }; #endregion Hair #region SpawnPriority foreach (var value in Enum.GetValues()) { _spawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value); } _spawnPriorityButton.OnItemSelected += args => { _spawnPriorityButton.SelectId(args.Id); SetSpawnPriority((SpawnPriorityPreference) args.Id); }; #endregion SpawnPriority #region Eyes _eyesPicker.OnEyeColorPicked += newColor => { if (Profile is null) return; Profile = Profile.WithCharacterAppearance( Profile.Appearance.WithEyeColor(newColor)); CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor; SetDirty(); }; #endregion Eyes #endregion Appearance #region Jobs _tabContainer.SetTabTitle(1, Loc.GetString("humanoid-profile-editor-jobs-tab")); _preferenceUnavailableButton.AddItem( Loc.GetString("humanoid-profile-editor-preference-unavailable-stay-in-lobby-button"), (int) PreferenceUnavailableMode.StayInLobby); _preferenceUnavailableButton.AddItem( Loc.GetString("humanoid-profile-editor-preference-unavailable-spawn-as-overflow-button", ("overflowJob", Loc.GetString(SharedGameTicker.FallbackOverflowJobName))), (int) PreferenceUnavailableMode.SpawnAsOverflow); _preferenceUnavailableButton.OnItemSelected += args => { _preferenceUnavailableButton.SelectId(args.Id); Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id); SetDirty(); }; _jobPriorities = new List(); _jobCategories = new Dictionary(); _requirements = IoCManager.Resolve(); // TODO: Move this to the LobbyUIController instead of being spaghetti everywhere. _requirements.Updated += UpdateAntagRequirements; _requirements.Updated += UpdateRoleRequirements; UpdateAntagRequirements(); UpdateRoleRequirements(); #endregion Jobs _tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab")); #region Traits var traits = prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); _traitPreferences = new List(); _tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); if (traits.Count > 0) { foreach (var trait in traits) { var selector = new TraitPreferenceSelector(trait); _traitsList.AddChild(selector); _traitPreferences.Add(selector); selector.PreferenceChanged += preference => { Profile = Profile?.WithTraitPreference(trait.ID, preference); SetDirty(); }; } } else { _traitsList.AddChild(new Label { Text = "No traits available :(", FontColorOverride = Color.Gray, }); } #endregion #region Save _saveButton.OnPressed += _ => { Save(); }; #endregion Save #region Markings _tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab")); CMarkings.OnMarkingAdded += OnMarkingChange; CMarkings.OnMarkingRemoved += OnMarkingChange; CMarkings.OnMarkingColorChange += OnMarkingChange; CMarkings.OnMarkingRankChange += OnMarkingChange; #endregion Markings #region FlavorText if (configurationManager.GetCVar(CCVars.FlavorText)) { var flavorText = new FlavorText.FlavorText(); _tabContainer.AddChild(flavorText); _tabContainer.SetTabTitle(_tabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab")); _flavorTextEdit = flavorText.CFlavorTextInput; flavorText.OnFlavorTextChanged += OnFlavorTextChange; } #endregion FlavorText #region Dummy _previewRotateLeftButton.OnPressed += _ => { _previewRotation = _previewRotation.TurnCw(); SetPreviewRotation(_previewRotation); }; _previewRotateRightButton.OnPressed += _ => { _previewRotation = _previewRotation.TurnCcw(); SetPreviewRotation(_previewRotation); }; #endregion Dummy #endregion Left if (preferencesManager.ServerDataLoaded) { LoadServerData(); } ShowClothes.OnToggled += args => { var lobby = UserInterfaceManager.GetUIController(); lobby.SetClothes(args.Pressed); SetDirty(); }; preferencesManager.OnServerDataLoaded += LoadServerData; SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed; UpdateSpeciesGuidebookIcon(); IsDirty = false; controller.UpdateProfile(); } private void SetDirty() { var controller = UserInterfaceManager.GetUIController(); controller.UpdateProfile(Profile); controller.ReloadCharacterUI(); IsDirty = true; } private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args) { var guidebookController = UserInterfaceManager.GetUIController(); var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies; var page = DefaultSpeciesGuidebook; if (_prototypeManager.HasIndex(species)) page = species; if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot)) { var dict = new Dictionary(); dict.Add(DefaultSpeciesGuidebook, guideRoot); //TODO: Don't close the guidebook if its already open, just go to the correct page guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page); } } private void OnDummyUpdate(EntityUid value) { _previewSpriteView.SetEntity(value); } private void UpdateAntagRequirements() { _antagList.DisposeAllChildren(); _antagPreferences.Clear(); var btnGroup = new ButtonGroup(); foreach (var antag in _prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name))) { if (!antag.SetPreference) continue; var selector = new AntagPreferenceSelector(antag, btnGroup) { Margin = new Thickness(3f, 3f, 3f, 0f), }; _antagList.AddChild(selector); _antagPreferences.Add(selector); if (selector.Disabled) { Profile = Profile?.WithAntagPreference(antag.ID, false); SetDirty(); } selector.PreferenceChanged += preference => { Profile = Profile?.WithAntagPreference(antag.ID, preference); SetDirty(); }; } } private void UpdateRoleRequirements() { _jobList.DisposeAllChildren(); _jobPriorities.Clear(); _jobCategories.Clear(); var firstCategory = true; var departments = _prototypeManager.EnumeratePrototypes().ToArray(); Array.Sort(departments, DepartmentUIComparer.Instance); foreach (var department in departments) { var departmentName = Loc.GetString($"department-{department.ID}"); if (!_jobCategories.TryGetValue(department.ID, out var category)) { category = new BoxContainer { Orientation = LayoutOrientation.Vertical, Name = department.ID, ToolTip = Loc.GetString("humanoid-profile-editor-jobs-amount-in-department-tooltip", ("departmentName", departmentName)) }; if (firstCategory) { firstCategory = false; } else { category.AddChild(new Control { MinSize = new Vector2(0, 23), }); } category.AddChild(new PanelContainer { PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#464966")}, Children = { new Label { Text = Loc.GetString("humanoid-profile-editor-department-jobs-label", ("departmentName", departmentName)), Margin = new Thickness(5f, 0, 0, 0) } } }); _jobCategories[department.ID] = category; _jobList.AddChild(category); } var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId)) .Where(job => job.SetPreference) .ToArray(); Array.Sort(jobs, JobUIComparer.Instance); var jobLoadoutGroup = new ButtonGroup(); foreach (var job in jobs) { RoleLoadout? loadout = null; // Clone so we don't modify the underlying loadout. Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout); loadout = loadout?.Clone(); var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager) { Margin = new Thickness(3f, 3f, 3f, 0f), }; if (!_requirements.IsAllowed(job, out var reason)) { selector.LockRequirements(reason); } category.AddChild(selector); _jobPriorities.Add(selector); selector.LoadoutUpdated += args => { Profile = Profile?.WithLoadout(args); SetDirty(); }; selector.PriorityChanged += priority => { Profile = Profile?.WithJobPriority(job.ID, priority); foreach (var jobSelector in _jobPriorities) { // Sync other selectors with the same job in case of multiple department jobs if (jobSelector.Proto == selector.Proto) { jobSelector.Priority = priority; } else if (priority == JobPriority.High && jobSelector.Priority == JobPriority.High) { // Lower any other high priorities to medium. jobSelector.Priority = JobPriority.Medium; Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium); } } SetDirty(); }; } } if (Profile is not null) { UpdateJobPriorities(); } } private void OnFlavorTextChange(string content) { if (Profile is null) return; Profile = Profile.WithFlavorText(content); SetDirty(); } private void OnMarkingChange(MarkingSet markings) { if (Profile is null) return; Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList())); IsDirty = true; var controller = UserInterfaceManager.GetUIController(); controller.UpdateProfile(Profile); controller.ReloadProfile(); } private void OnSkinColorOnValueChanged() { if (Profile is null) return; var skin = _prototypeManager.Index(Profile.Species).SkinColoration; switch (skin) { case HumanoidSkinColor.HumanToned: { if (!_skinColor.Visible) { _skinColor.Visible = true; _rgbSkinColorContainer.Visible = false; } var color = SkinColor.HumanSkinTone((int) _skinColor.Value); CMarkings.CurrentSkinColor = color; Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));// break; } case HumanoidSkinColor.Hues: { if (!_rgbSkinColorContainer.Visible) { _skinColor.Visible = false; _rgbSkinColorContainer.Visible = true; } CMarkings.CurrentSkinColor = _rgbSkinColorSelector.Color; Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color)); break; } case HumanoidSkinColor.TintedHues: { if (!_rgbSkinColorContainer.Visible) { _skinColor.Visible = false; _rgbSkinColorContainer.Visible = true; } var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color); CMarkings.CurrentSkinColor = color; Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color)); break; } } IsDirty = true; var controller = UserInterfaceManager.GetUIController(); controller.UpdateProfile(Profile); controller.ReloadProfile(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) return; var controller = UserInterfaceManager.GetUIController(); controller.PreviewDummyUpdated -= OnDummyUpdate; _requirements.Updated -= UpdateAntagRequirements; _requirements.Updated -= UpdateRoleRequirements; _preferencesManager.OnServerDataLoaded -= LoadServerData; } public void LoadServerData() { Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter; CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex; UpdateAntagRequirements(); UpdateRoleRequirements(); UpdateControls(); ShowClothes.Pressed = true; } private void SetAge(int newAge) { Profile = Profile?.WithAge(newAge); SetDirty(); } 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(); CMarkings.SetSex(newSex); SetDirty(); } private void SetGender(Gender newGender) { Profile = Profile?.WithGender(newGender); SetDirty(); } private void SetSpecies(string newSpecies) { 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 UpdateSpeciesGuidebookIcon(); SetDirty(); UpdatePreview(); } private void SetName(string newName) { Profile = Profile?.WithName(newName); SetDirty(); } private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority) { Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority); SetDirty(); } public void Save() { IsDirty = false; if (Profile == null) return; _preferencesManager.UpdateCharacter(Profile, CharacterSlot); OnProfileChanged?.Invoke(Profile, CharacterSlot); // Reset profile to default. UserInterfaceManager.GetUIController().UpdateProfile(); } private bool IsDirty { get => _isDirty; set { _isDirty = value; UpdateSaveButton(); } } private void UpdateNameEdit() { _nameEdit.Text = Profile?.Name ?? ""; } private void UpdateFlavorTextEdit() { if(_flavorTextEdit != null) { _flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? ""); } } private void UpdateAgeEdit() { _ageEdit.Text = Profile?.Age.ToString() ?? ""; } private void UpdateSexControls() { 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 _sexButton.SelectId((int) sexes[0]); } private void UpdateSkinColor() { if (Profile == null) return; var skin = _prototypeManager.Index(Profile.Species).SkinColoration; switch (skin) { case HumanoidSkinColor.HumanToned: { if (!_skinColor.Visible) { _skinColor.Visible = true; _rgbSkinColorContainer.Visible = false; } _skinColor.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor); break; } case HumanoidSkinColor.Hues: { if (!_rgbSkinColorContainer.Visible) { _skinColor.Visible = false; _rgbSkinColorContainer.Visible = true; } // set the RGB values to the direct values otherwise _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor; break; } case HumanoidSkinColor.TintedHues: { if (!_rgbSkinColorContainer.Visible) { _skinColor.Visible = false; _rgbSkinColorContainer.Visible = true; } // set the RGB values to the direct values otherwise _rgbSkinColorSelector.Color = Profile.Appearance.SkinColor; break; } } } public void UpdateSpeciesGuidebookIcon() { SpeciesInfoButton.StyleClasses.Clear(); var species = Profile?.Species; if (species is null) return; if (!_prototypeManager.TryIndex(species, out var speciesProto)) return; // Don't display the info button if no guide entry is found if (!_prototypeManager.HasIndex(species)) return; const string style = "SpeciesInfoDefault"; SpeciesInfoButton.StyleClasses.Add(style); } private void UpdateMarkings() { if (Profile == null) { return; } CMarkings.SetData(Profile.Appearance.Markings, Profile.Species, Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor ); } private void UpdateSpecies() { if (Profile == null) { return; } CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species)); } private void UpdateGenderControls() { if (Profile == null) { return; } _genderButton.SelectId((int) Profile.Gender); } private void UpdateSpawnPriorityControls() { if (Profile == null) { return; } _spawnPriorityButton.SelectId((int) Profile.SpawnPriority); } private void UpdateHairPickers() { if (Profile == null) { return; } var hairMarking = Profile.Appearance.HairStyleId switch { HairStyles.DefaultHairStyle => new List(), _ => new() { new(Profile.Appearance.HairStyleId, new List() { Profile.Appearance.HairColor }) }, }; var facialHairMarking = Profile.Appearance.FacialHairStyleId switch { HairStyles.DefaultFacialHairStyle => new List(), _ => new() { new(Profile.Appearance.FacialHairStyleId, new List() { Profile.Appearance.FacialHairColor }) }, }; _hairPicker.UpdateData( hairMarking, Profile.Species, 1); _facialHairPicker.UpdateData( facialHairMarking, Profile.Species, 1); } private void UpdateCMarkingsHair() { if (Profile == null) { return; } // hair color Color? hairColor = null; if ( Profile.Appearance.HairStyleId != HairStyles.DefaultHairStyle && _markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto) ) { if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, hairProto, _prototypeManager)) { if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager)) { hairColor = Profile.Appearance.SkinColor; } else { hairColor = Profile.Appearance.HairColor; } } } if (hairColor != null) { CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List() { hairColor.Value }); } else { CMarkings.HairMarking = null; } } private void UpdateCMarkingsFacialHair() { if (Profile == null) { return; } // facial hair color Color? facialHairColor = null; if ( Profile.Appearance.FacialHairStyleId != HairStyles.DefaultFacialHairStyle && _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto) ) { if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, facialHairProto, _prototypeManager)) { if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager)) { facialHairColor = Profile.Appearance.SkinColor; } else { facialHairColor = Profile.Appearance.FacialHairColor; } } } if (facialHairColor != null) { CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List() { facialHairColor.Value }); } else { CMarkings.FacialHairMarking = null; } } private void UpdateEyePickers() { if (Profile == null) { return; } CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor; _eyesPicker.SetData(Profile.Appearance.EyeColor); } private void UpdateSaveButton() { _saveButton.Disabled = Profile is null || !IsDirty; } private void UpdatePreview() { if (Profile is null) return; UserInterfaceManager.GetUIController().ReloadProfile(); SetPreviewRotation(_previewRotation); } private void SetPreviewRotation(Direction direction) { _previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2); } public void UpdateControls() { if (Profile is null) return; UpdateNameEdit(); UpdateFlavorTextEdit(); UpdateSexControls(); UpdateGenderControls(); UpdateSkinColor(); UpdateSpecies(); UpdateSpawnPriorityControls(); UpdateAgeEdit(); UpdateEyePickers(); UpdateSaveButton(); UpdateLoadouts(); UpdateJobPriorities(); UpdateAntagPreferences(); UpdateTraitPreferences(); UpdateMarkings(); UpdateHairPickers(); UpdateCMarkingsHair(); UpdateCMarkingsFacialHair(); _preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable); } private void UpdateJobPriorities() { foreach (var prioritySelector in _jobPriorities) { var jobId = prioritySelector.Proto.ID; var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never; prioritySelector.Priority = priority; } } private void UpdateLoadouts() { foreach (var prioritySelector in _jobPriorities) { prioritySelector.CloseLoadout(); } } private void UpdateAntagPreferences() { foreach (var preferenceSelector in _antagPreferences) { var antagId = preferenceSelector.Proto.ID; var preference = Profile?.AntagPreferences.Contains(antagId) ?? false; preferenceSelector.Preference = preference; } } private void UpdateTraitPreferences() { foreach (var preferenceSelector in _traitPreferences) { var traitId = preferenceSelector.Trait.ID; var preference = Profile?.TraitPreferences.Contains(traitId) ?? false; preferenceSelector.Preference = preference; } } private sealed class TraitPreferenceSelector : Control { public TraitPrototype Trait { get; } private readonly CheckBox _checkBox; public bool Preference { get => _checkBox.Pressed; set => _checkBox.Pressed = value; } public event Action? PreferenceChanged; public TraitPreferenceSelector(TraitPrototype trait) { Trait = trait; _checkBox = new CheckBox {Text = Loc.GetString(trait.Name)}; _checkBox.OnToggled += OnCheckBoxToggled; if (trait.Description is { } desc) { _checkBox.ToolTip = Loc.GetString(desc); } AddChild(new BoxContainer { Orientation = LayoutOrientation.Horizontal, Children = { _checkBox }, }); } private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) { PreferenceChanged?.Invoke(Preference); } } } }