Files
space-station-14/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
portfiend 15147dfcdf Split HumanoidProfileEditor.xaml.cs into separate files (#42715)
* separate humanoid profile editor into different files

* move this to the rest of the fields
2026-02-09 20:12:07 +00:00

436 lines
14 KiB
C#

using Content.Client.Humanoid;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Sprite;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
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.ContentPack;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Lobby.UI
{
[GenerateTypedNameReferences]
public sealed partial class HumanoidProfileEditor : BoxContainer
{
private readonly IClientPreferencesManager _preferencesManager;
private readonly IConfigurationManager _cfgManager;
private readonly IEntityManager _entManager;
private readonly IFileDialogManager _dialogManager;
private readonly IPlayerManager _playerManager;
private readonly IPrototypeManager _prototypeManager;
private readonly IResourceManager _resManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private readonly LobbyUIController _controller;
private readonly SpriteSystem _sprite;
// CCvar.
private int _maxNameLength;
/// <summary>
/// If we're attempting to save.
/// </summary>
public event Action? Save;
/// <summary>
/// The character slot for the current profile.
/// </summary>
public int? CharacterSlot;
/// <summary>
/// The work in progress profile being edited.
/// </summary>
public HumanoidCharacterProfile? Profile;
private Direction _previewRotation = Direction.North;
private bool _isDirty;
public bool IsDirty
{
get => _isDirty;
set
{
if (_isDirty == value)
return;
_isDirty = value;
UpdateSaveButton();
}
}
private ISawmill _sawmill;
private MarkingsViewModel _markingsModel = new();
public HumanoidProfileEditor(
IClientPreferencesManager preferencesManager,
IConfigurationManager configurationManager,
IEntityManager entManager,
IFileDialogManager dialogManager,
ILogManager logManager,
IPlayerManager playerManager,
IPrototypeManager prototypeManager,
IResourceManager resManager,
JobRequirementsManager requirements,
MarkingManager markings)
{
RobustXamlLoader.Load(this);
_sawmill = logManager.GetSawmill("profile.editor");
_cfgManager = configurationManager;
_entManager = entManager;
_dialogManager = dialogManager;
_playerManager = playerManager;
_prototypeManager = prototypeManager;
_markingManager = markings;
_preferencesManager = preferencesManager;
_resManager = resManager;
_requirements = requirements;
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
_sprite = _entManager.System<SpriteSystem>();
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
_allowFlavorText = _cfgManager.GetCVar(CCVars.FlavorText);
Markings.SetModel(_markingsModel);
ImportButton.OnPressed += args =>
{
ImportProfile();
};
ExportButton.OnPressed += args =>
{
ExportProfile();
};
ExportImageButton.OnPressed += args =>
{
ExportImage();
};
OpenImagesButton.OnPressed += args =>
{
_resManager.UserData.OpenOsWindow(ContentSpriteSystem.Exports);
};
ResetButton.OnPressed += args =>
{
SetProfile((HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, _preferencesManager.Preferences?.SelectedCharacterIndex);
};
SaveButton.OnPressed += args =>
{
Save?.Invoke();
};
#region Left
#region Name
NameEdit.OnTextChanged += args => { SetName(args.Text); };
NameEdit.IsValid = args => args.Length <= _maxNameLength;
NameRandomize.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
PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int)Gender.Male);
PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int)Gender.Female);
PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int)Gender.Epicene);
PronounsButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int)Gender.Neuter);
PronounsButton.OnItemSelected += args =>
{
PronounsButton.SelectId(args.Id);
SetGender((Gender)args.Id);
};
#endregion Gender
RefreshSpecies();
SpeciesButton.OnItemSelected += args =>
{
SpeciesButton.SelectId(args.Id);
SetSpecies(_species[args.Id].ID);
OnSkinColorOnValueChanged();
};
#region Skin
Skin.OnValueChanged += _ =>
{
OnSkinColorOnValueChanged();
};
RgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
_rgbSkinColorSelector.SelectorType = ColorSelectorSliders.ColorSelectorType.Hsv; // defaults color selector to HSV
_rgbSkinColorSelector.OnColorChanged += _ =>
{
OnSkinColorOnValueChanged();
};
#endregion
#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
EyeColorPicker.OnEyeColorPicked += newColor =>
{
if (Profile is null)
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithEyeColor(newColor));
_markingsModel.SetOrganEyeColor(Profile.Appearance.EyeColor);
ReloadProfilePreview();
};
#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();
};
_jobCategories = new Dictionary<string, BoxContainer>();
RefreshAntags();
RefreshJobs();
#endregion Jobs
TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
RefreshTraits();
#region Markings
TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
_markingsModel.MarkingsChanged += (_, _) => OnMarkingChange();
_markingsModel.MarkingsReset += OnMarkingChange;
#endregion Markings
RefreshFlavorText();
#region Dummy
SpriteRotateLeft.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCw();
SetPreviewRotation(_previewRotation);
};
SpriteRotateRight.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCcw();
SetPreviewRotation(_previewRotation);
};
#endregion Dummy
#endregion Left
ShowClothes.OnToggled += args =>
{
ReloadPreview();
};
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
UpdateSpeciesGuidebookIcon();
IsDirty = false;
}
private void SetDirty()
{
// If it equals default then reset the button.
if (Profile == null || _preferencesManager.Preferences?.SelectedCharacter.MemberwiseEquals(Profile) == true)
{
IsDirty = false;
return;
}
// TODO: Check if profile matches default.
IsDirty = true;
}
/// <summary>
/// Reloads the entire dummy entity for preview.
/// </summary>
/// <remarks>
/// This is expensive so not recommended to run if you have a slider.
/// </remarks>
private void ReloadPreview()
{
if (Profile == null)
return;
SpriteView.LoadPreview(Profile, JobOverride, ShowClothes.Pressed);
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
SetDirty();
}
/// <summary>
/// Resets the profile to the defaults.
/// </summary>
public void ResetToDefault()
{
SetProfile(
(HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter,
_preferencesManager.Preferences?.SelectedCharacterIndex);
}
/// <summary>
/// Sets the editor to the specified profile with the specified slot.
/// </summary>
public void SetProfile(HumanoidCharacterProfile? profile, int? slot)
{
Profile = profile?.Clone();
CharacterSlot = slot;
IsDirty = false;
JobOverride = null;
UpdateNameEdit();
UpdateFlavorTextEdit();
UpdateSexControls();
UpdateGenderControls();
UpdateSkinColor();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();
UpdateMarkings();
RefreshAntags();
RefreshJobs();
RefreshLoadouts();
RefreshSpecies();
RefreshTraits();
RefreshFlavorText();
ReloadPreview();
if (Profile != null)
{
PreferenceUnavailableButton.SelectId((int)Profile.PreferenceUnavailable);
}
}
/// <summary>
/// A slim reload that only updates the entity itself and not any of the job entities, etc.
/// </summary>
private void ReloadProfilePreview()
{
if (Profile == null)
return;
SpriteView.ReloadProfilePreview(Profile);
// Check and set the dirty flag to enable the save/reset buttons as appropriate.
SetDirty();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_loadoutWindow?.Dispose();
_loadoutWindow = null;
}
protected override void EnteredTree()
{
base.EnteredTree();
ReloadPreview();
}
private void UpdateSaveButton()
{
SaveButton.Disabled = Profile is null || !IsDirty;
ResetButton.Disabled = Profile is null || !IsDirty;
}
private void SetPreviewRotation(Direction direction)
{
SpriteView.OverrideDirection = (Direction)((int)direction % 4 * 2);
}
}
}