Files
OldThink/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs

1508 lines
51 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Client._White.Sponsors;
using Content.Client.Administration.Managers;
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.Shared._Amour.RoleplayInfo;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.Administration;
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.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Preferences.UI
{
[GenerateTypedNameReferences]
public sealed partial class HumanoidProfileEditor : Control
{
//WD-EDIT
private readonly SponsorsManager _sponsorsManager;
private readonly IClientAdminManager _adminManager;
//WD-EDIT
private readonly IPrototypeManager _prototypeManager;
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entMan = default!;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
private TextEdit? _flavorTextEdit;
private LineEdit _nameClownEdit => CClownNameEdit;
private LineEdit _nameMimeEdit => CMimeNameEdit;
private LineEdit _nameBorgEdit => CBorgNameEdit;
private Button _nameRandomButton => CNameRandomize;
private Button _nameClownRandomButton => CClownNameRandomize;
private Button _nameMimeRandomButton => CMimeNameRandomize;
private Button _nameBorgRandomButton => CBorgNameRandomize;
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
private Button _saveButton => CSaveButton;
private OptionButton _sexButton => CSexButton;
private OptionButton _genderButton => CPronounsButton;
//WD-EDIT
private OptionButton _voiceButton => CVoiceButton;
private Button _voicePlayButton => CVoicePlayButton;
private BoxContainer _rolePlayThink => CRolePlayThing;
private List<RolePlaySelector> _roleplaySelections = new();
//WD-EDIT
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<JobPrioritySelector> _jobPriorities;
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
private readonly Dictionary<string, BoxContainer> _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<SpeciesPrototype> _speciesList;
private readonly List<AntagPreferenceSelector> _antagPreferences = new();
private readonly List<TraitPreferenceSelector> _traitPreferences;
private List<BodyTypePrototype> _bodyTypesList = new();
private SpriteView _previewSpriteView => CSpriteView;
private Button _previewRotateLeftButton => CSpriteRotateLeft;
private Button _previewRotateRightButton => CSpriteRotateRight;
private Direction _previewRotation = Direction.North;
private EntityUid? _previewDummy;
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
private bool _needUpdatePreview;
public int CharacterSlot;
public HumanoidCharacterProfile? Profile;
private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
private static LobbyUIController? _controller;
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
[ValidatePrototypeId<GuideEntryPrototype>]
private const string DefaultSpeciesGuidebook = "Species";
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_sponsorsManager = IoCManager.Resolve<SponsorsManager>();
_adminManager = IoCManager.Resolve<IClientAdminManager>();
_prototypeManager = prototypeManager;
_preferencesManager = preferencesManager;
_markingManager = IoCManager.Resolve<MarkingManager>();
_entMan = IoCManager.Resolve<IEntityManager>(); // WD
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
_controller.PreviewDummyUpdated += OnDummyUpdate;
_previewSpriteView.SetEntity(_controller.GetPreviewDummy());
#region Left
#region Name
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
_nameClownEdit.OnTextChanged += args => { SetClownName(args.Text); };
_nameMimeEdit.OnTextChanged += args => { SetMimeName(args.Text); };
_nameBorgEdit.OnTextChanged += args => { SetBorgName(args.Text); };
_nameRandomButton.OnPressed += args => RandomizeName();
_nameClownRandomButton.OnPressed += args => RandomizeClownName();
_nameMimeRandomButton.OnPressed += args => RandomizeMimeName();
_nameBorgRandomButton.OnPressed += args => RandomizeBorgName();
_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 Body Type
CBodyTypesButton.OnItemSelected += OnBodyTypeSelected;
UpdateBodyTypes();
#endregion Body Type
#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
//TTS-Start
#region Voice
InitializeVoice();
#endregion
//TTS-End
//AMOUR START
#region Genitals
InitializeGenitals();
#endregion
#region height
InitializeHeight();
#endregion
//AMOUR END
#region Species
//WD EDIT
_speciesList = GetAllowedSpecies();
//WD EDIT END
for (var i = 0; i < _speciesList.Count; i++)
{
var specie = _speciesList[i]; // WD EDIT
var name = Loc.GetString(specie.Name);
CSpeciesButton.AddItem(name, i);
}
CSpeciesButton.OnItemSelected += args =>
{
CSpeciesButton.SelectId(args.Id);
SetSpecies(_speciesList[args.Id].ID);
UpdateHairPickers();
OnSkinColorOnValueChanged();
UpdateHeightControl(); // AMOUR
};
#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<SpawnPriorityPreference>())
{
_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<JobPrioritySelector>();
_jobCategories = new Dictionary<string, BoxContainer>();
_requirements = IoCManager.Resolve<JobRequirementsManager>();
// TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
_requirements.Updated += UpdateAntagRequirements;
_requirements.Updated += UpdateRoleRequirements;
UpdateAntagRequirements();
UpdateRoleRequirements();
#endregion Jobs
#region Antags
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
#endregion Antags
#region Traits
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
_traitPreferences = new List<TraitPreferenceSelector>();
_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
//WD EDIT
#region RolePlayThink
_tabContainer.SetTabTitle(5, Loc.GetString("roleplay-tab"));
_rolePlayThink.DisposeAllChildren();
_roleplaySelections.Clear();
foreach (var proto in prototypeManager.EnumeratePrototypes<RoleplayInfoPrototype>())
{
var think = new RolePlaySelector(proto.ID);
think.PreferenceChanged += selection =>
{
Profile = Profile?.WithRoleplaySelection(proto.ID, selection);
IsDirty = true;
};
_roleplaySelections.Add(think);
_rolePlayThink.Children.Add(think);
}
#endregion
//WD END EDIT
#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<LobbyUIController>();
lobby.SetClothes(args.Pressed);
SetDirty();
};
preferencesManager.OnServerDataLoaded += LoadServerData;
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
UpdateSpeciesGuidebookIcon();
SetDirty();
}
private void SetDirty()
{
if (_controller == null)
return;
_controller.UpdateProfile(Profile);
_controller.ReloadCharacterUI();
IsDirty = true;
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
page = species;
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
{
var dict = new Dictionary<string, GuideEntry>();
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();
var character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter;
if (character == null)
return;
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
{
if (!antag.SetPreference)
continue;
var selector = new AntagPreferenceSelector(antag, btnGroup, character)
{
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 character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter;
if (character == null)
return;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().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("#232323")},
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<JobPrototype>(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, character)
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, 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()));
SetDirty();
}
private void OnSkinColorOnValueChanged()
{
if (Profile is null)
return;
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
switch (skin)
{
case HumanoidSkinColor.HumanToned:
_skinColor.Visible = true;
_rgbSkinColorContainer.Visible = false;
ApplySkinColor(Profile, SkinColor.HumanSkinTone((int)_skinColor.Value));
break;
case HumanoidSkinColor.Hues:
case HumanoidSkinColor.TintedHues:
_skinColor.Visible = false;
_rgbSkinColorContainer.Visible = true;
var color = skin == HumanoidSkinColor.Hues
? _rgbSkinColorSelector.Color
: SkinColor.TintedHues(_rgbSkinColorSelector.Color);
ApplySkinColor(Profile, color);
break;
}
SetDirty();
}
private void ApplySkinColor(HumanoidCharacterProfile profile, Color color)
{
CMarkings.CurrentSkinColor = color;
Profile = profile.WithCharacterAppearance(profile.Appearance.WithSkinColor(color));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
if (_controller == null)
return;
_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();
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);
UpdateTTSVoicesControls(); //WD-EDIT
SetDirty();
}
//WD-EDIT
private void SetVoice(string newVoice)
{
Profile = Profile?.WithVoice(newVoice);
SetDirty();
}
//WD-EDIT
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();
UpdateBodyTypes();
SetDirty();
UpdatePreview();
}
private void SetName(string newName)
{
Profile = Profile?.WithName(newName);
SetDirty();
}
private void SetClownName(string newName)
{
Profile = Profile?.WithClownName(newName);
SetDirty();
}
private void SetMimeName(string newName)
{
Profile = Profile?.WithMimeName(newName);
SetDirty();
}
private void SetBorgName(string newName)
{
Profile = Profile?.WithBorgName(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<LobbyUIController>().UpdateProfile();
}
private void OnBodyTypeSelected(OptionButton.ItemSelectedEventArgs args)
{
args.Button.SelectId(args.Id);
SetBodyType(_bodyTypesList[args.Id].ID);
}
private void UpdateBodyTypes()
{
if (Profile is null)
return;
CBodyTypesButton.Clear();
var species = _prototypeManager.Index<SpeciesPrototype>(Profile.Species);
var sex = Profile.Sex;
_bodyTypesList = EntitySystem.Get<HumanoidAppearanceSystem>().GetValidBodyTypes(species, sex);
for (var i = 0; i < _bodyTypesList.Count; i++)
{
CBodyTypesButton.AddItem(Loc.GetString(_bodyTypesList[i].Name), i);
}
// If current body type is not valid.
if (!_bodyTypesList.Select(proto => proto.ID).Contains(Profile.BodyType.Id))
{
// Then replace it with a first valid body type.
SetBodyType(_bodyTypesList.First().ID);
}
CBodyTypesButton.Select(_bodyTypesList.FindIndex(x => x.ID == Profile.BodyType));
SetDirty();
}
private bool IsDirty
{
get => _isDirty;
set
{
_isDirty = value;
UpdateSaveButton();
}
}
private void UpdateNamesEdit()
{
_nameEdit.Text = Profile?.Name ?? "";
_nameClownEdit.Text = Profile?.ClownName ?? "";
_nameMimeEdit.Text = Profile?.MimeName ?? "";
_nameBorgEdit.Text = Profile?.BorgName ?? "";
}
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<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
_sexButton.SelectId((int) sexes[0]);
}
private void UpdateSkinColor()
{
if (Profile == null)
return;
var skin = _prototypeManager.Index<SpeciesPrototype>(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<SpeciesPrototype>(species, out var speciesProto))
return;
// Don't display the info button if no guide entry is found
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(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.BodyType, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
);
}
private void UpdateSpecies()
{
if (Profile == null)
{
return;
}
if (!_speciesList.Exists(x => x.ID == Profile.Species))
{
CSpeciesButton.Select(0);
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<Marking>(),
_ => new List<Marking> { new(Profile.Appearance.HairStyleId, new List<Color> { Profile.Appearance.HairColor }) },
};
var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
{
HairStyles.DefaultFacialHairStyle => new List<Marking>(),
_ => new List<Marking> { new(Profile.Appearance.FacialHairStyleId, new List<Color> { 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))
{
hairColor = _markingManager.MustMatchSkin(Profile.BodyType, HumanoidVisualLayers.Hair, out _, _prototypeManager)
? Profile.Appearance.SkinColor
: Profile.Appearance.HairColor;
}
}
if (hairColor != null)
{
CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color> { 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.BodyType, HumanoidVisualLayers.Hair, out _, _prototypeManager))
{
facialHairColor = Profile.Appearance.SkinColor;
}
else
{
facialHairColor = Profile.Appearance.FacialHairColor;
}
}
}
if (facialHairColor != null)
{
CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color> { 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<LobbyUIController>().ReloadProfile();
SetPreviewRotation(_previewRotation);
}
private void SetPreviewRotation(Direction direction)
{
_previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
public void UpdateControls()
{
if (Profile is null)
return;
UpdateNamesEdit();
UpdateFlavorTextEdit();
UpdateSexControls();
UpdateGenderControls();
UpdateSkinColor();
UpdateSpecies();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();
UpdateLoadouts();
UpdateRoleRequirements();
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateMarkings();
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
//WD-EDIT
UpdateTTSVoicesControls();
UpdateBodyTypes();
//WD-EDIT
//Amour edit
UpdateGenitalsControls();
UpdateRoleplayThink();
UpdateHeightControl();
//Amour edit
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_needUpdatePreview)
{
UpdatePreview();
_needUpdatePreview = false;
}
}
//WD EDIT
private List<SpeciesPrototype> GetAllowedSpecies()
{
var allowedSpecies = new List<SpeciesPrototype>();
var rawSpecieList = _prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(specie =>
{
switch (specie.RoundStart)
{
case true when specie.SponsorOnly || specie.ForAdmins:
return true;
case true:
allowedSpecies.Add(specie);
return false;
default:
return false;
}
}).ToList();
if (_sponsorsManager.TryGetInfo(out var sponsor))
{
foreach (var specie in rawSpecieList)
{
if (specie.SponsorOnly
&& sponsor.AllowedMarkings.Contains(specie.ID)
&& !allowedSpecies.Contains(specie))
{
allowedSpecies.Add(specie);
}
}
}
if (_adminManager.HasFlag(AdminFlags.AdminSpecies))
{
foreach (var specie in rawSpecieList)
{
if (specie.ForAdmins && !allowedSpecies.Contains(specie))
{
allowedSpecies.Add(specie);
}
}
}
return allowedSpecies;
}
private void SetBodyType(string newBodyType)
{
Profile = Profile?.WithBodyType(newBodyType);
SetDirty();
_needUpdatePreview = true;
}
//WD EDIT END
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<bool>? 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);
}
}
private sealed class RolePlaySelector : Control
{
private readonly RadioOptions<RoleplaySelection> _options;
public RoleplaySelection Preference
{
get => _options.SelectedValue;
set => _options.SelectByValue(value);
}
public string RolePlayId { get; }
public event Action<RoleplaySelection>? PreferenceChanged;
public RolePlaySelector(string rolePlayId)
{
RolePlayId = rolePlayId;
_options = new RadioOptions<RoleplaySelection>(RadioOptionsLayout.Horizontal)
{
FirstButtonStyle = StyleBase.ButtonOpenRight,
ButtonStyle = StyleBase.ButtonOpenBoth,
LastButtonStyle = StyleBase.ButtonOpenLeft
};
_options.GenerateItem = (text, _) => new Button
{
Text = text,
MinWidth = 90
};
_options.OnItemSelected += args => _options.Select(args.Id);
var titleLabel = new Label()
{
Margin = new Thickness(5f, 0, 5f, 0),
Text = Loc.GetString("roleplay-name-" + rolePlayId.ToLower()),
MouseFilter = MouseFilterMode.Stop,
ToolTip = Loc.GetString("roleplay-desc-" + rolePlayId.ToLower())
};
_options.OnItemSelected += _ => PreferenceChanged?.Invoke(Preference);
_options.AddItem(Loc.GetString("roleplay-no"), RoleplaySelection.No);
_options.AddItem(Loc.GetString("roleplay-maybe"), RoleplaySelection.Maybe);
_options.AddItem(Loc.GetString("roleplay-yes"), RoleplaySelection.Yes);
titleLabel.HorizontalAlignment = HAlignment.Left;
_options.HorizontalAlignment = HAlignment.Center;
AddChild(titleLabel);
AddChild(_options);
HorizontalExpand = true;
}
}
private void UpdateRoleplayThink()
{
if(Profile is null)
return;
foreach (var selector in _roleplaySelections)
{
if (Profile.RoleplayInfoData.TryGetValue(selector.RolePlayId, out var value))
{
selector.Preference = value.RoleplaySelection;
}
}
}
}
}