mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-15 03:31:30 +01:00
* separate humanoid profile editor into different files * move this to the rest of the fields
436 lines
14 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|