1517 lines
50 KiB
C#
1517 lines
50 KiB
C#
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.Guidebook;
|
|
using Content.Client.Humanoid;
|
|
using Content.Client.Lobby.UI.Loadouts;
|
|
using Content.Client.Lobby.UI.Roles;
|
|
using Content.Client.Message;
|
|
using Content.Client.Players.PlayTimeTracking;
|
|
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.Roles;
|
|
using Content.Shared.StatusIcon;
|
|
using Content.Shared.Traits;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.Graphics;
|
|
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.Enums;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
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 MarkingManager _markingManager;
|
|
private readonly JobRequirementsManager _requirements;
|
|
private readonly LobbyUIController _controller;
|
|
|
|
private FlavorText.FlavorText? _flavorText;
|
|
private TextEdit? _flavorTextEdit;
|
|
|
|
// One at a time.
|
|
private LoadoutWindow? _loadoutWindow;
|
|
|
|
private bool _exporting;
|
|
|
|
/// <summary>
|
|
/// If we're attempting to save.
|
|
/// </summary>
|
|
public event Action? Save;
|
|
|
|
/// <summary>
|
|
/// Entity used for the profile editor preview
|
|
/// </summary>
|
|
public EntityUid PreviewDummy;
|
|
|
|
/// <summary>
|
|
/// Temporary override of their selected job, used to preview roles.
|
|
/// </summary>
|
|
public JobPrototype? JobOverride;
|
|
|
|
/// <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 List<SpeciesPrototype> _species = new();
|
|
|
|
private List<(string, RequirementsSelector)> _jobPriorities = new();
|
|
|
|
private readonly Dictionary<string, BoxContainer> _jobCategories;
|
|
|
|
private Direction _previewRotation = Direction.North;
|
|
|
|
private ColorSelectorSliders _rgbSkinColorSelector;
|
|
|
|
private bool _isDirty;
|
|
|
|
[ValidatePrototypeId<GuideEntryPrototype>]
|
|
private const string DefaultSpeciesGuidebook = "Species";
|
|
|
|
private ISawmill _sawmill;
|
|
|
|
public HumanoidProfileEditor(
|
|
IClientPreferencesManager preferencesManager,
|
|
IConfigurationManager configurationManager,
|
|
IEntityManager entManager,
|
|
IFileDialogManager dialogManager,
|
|
ILogManager logManager,
|
|
IPlayerManager playerManager,
|
|
IPrototypeManager prototypeManager,
|
|
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;
|
|
_requirements = requirements;
|
|
_controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
|
|
ImportButton.OnPressed += args =>
|
|
{
|
|
ImportProfile();
|
|
};
|
|
|
|
ExportButton.OnPressed += args =>
|
|
{
|
|
ExportProfile();
|
|
};
|
|
|
|
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); };
|
|
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);
|
|
UpdateHairPickers();
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
#region Skin
|
|
|
|
Skin.OnValueChanged += _ =>
|
|
{
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
RgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
|
|
_rgbSkinColorSelector.OnColorChanged += _ =>
|
|
{
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region Hair
|
|
|
|
HairStylePicker.OnMarkingSelect += newStyle =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(newStyle.id));
|
|
ReloadPreview();
|
|
};
|
|
|
|
HairStylePicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsHair();
|
|
ReloadPreview();
|
|
};
|
|
|
|
FacialHairPicker.OnMarkingSelect += newStyle =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
|
|
ReloadPreview();
|
|
};
|
|
|
|
FacialHairPicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsFacialHair();
|
|
ReloadPreview();
|
|
};
|
|
|
|
HairStylePicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
ReloadPreview();
|
|
};
|
|
|
|
FacialHairPicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsFacialHair();
|
|
ReloadPreview();
|
|
};
|
|
|
|
HairStylePicker.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();
|
|
ReloadPreview();
|
|
};
|
|
|
|
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();
|
|
ReloadPreview();
|
|
};
|
|
|
|
#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
|
|
|
|
EyeColorPicker.OnEyeColorPicked += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithEyeColor(newColor));
|
|
Markings.CurrentEyeColor = 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);
|
|
};
|
|
|
|
_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"));
|
|
|
|
Markings.OnMarkingAdded += OnMarkingChange;
|
|
Markings.OnMarkingRemoved += OnMarkingChange;
|
|
Markings.OnMarkingColorChange += OnMarkingChange;
|
|
Markings.OnMarkingRankChange += 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();
|
|
ReloadPreview();
|
|
IsDirty = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the flavor text editor status.
|
|
/// </summary>
|
|
public void RefreshFlavorText()
|
|
{
|
|
if (_cfgManager.GetCVar(CCVars.FlavorText))
|
|
{
|
|
if (_flavorText != null)
|
|
return;
|
|
|
|
_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;
|
|
}
|
|
else
|
|
{
|
|
if (_flavorText == null)
|
|
return;
|
|
|
|
TabContainer.RemoveChild(_flavorText);
|
|
_flavorText.OnFlavorTextChanged -= OnFlavorTextChange;
|
|
_flavorText.Dispose();
|
|
_flavorTextEdit?.Dispose();
|
|
_flavorTextEdit = null;
|
|
_flavorText = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes traits selector
|
|
/// </summary>
|
|
public void RefreshTraits()
|
|
{
|
|
TraitsList.DisposeAllChildren();
|
|
|
|
var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
|
|
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
|
|
|
|
if (traits.Count > 0)
|
|
{
|
|
foreach (var trait in traits)
|
|
{
|
|
var selector = new TraitPreferenceSelector(trait);
|
|
|
|
if (Profile?.TraitPreferences.Contains(trait.ID) == true)
|
|
{
|
|
selector.Preference = true;
|
|
}
|
|
else
|
|
{
|
|
selector.Preference = false;
|
|
}
|
|
|
|
selector.PreferenceChanged += preference =>
|
|
{
|
|
Profile = Profile?.WithTraitPreference(trait.ID, preference);
|
|
SetDirty();
|
|
};
|
|
|
|
TraitsList.AddChild(selector);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraitsList.AddChild(new Label
|
|
{
|
|
// TODO: Localise
|
|
Text = "No traits available :(",
|
|
FontColorOverride = Color.Gray,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the species selector.
|
|
/// </summary>
|
|
public void RefreshSpecies()
|
|
{
|
|
SpeciesButton.Clear();
|
|
_species.Clear();
|
|
|
|
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
|
|
var speciesIds = _species.Select(o => o.ID).ToList();
|
|
|
|
for (var i = 0; i < _species.Count; i++)
|
|
{
|
|
var name = Loc.GetString(_species[i].Name);
|
|
SpeciesButton.AddItem(name, i);
|
|
|
|
if (Profile?.Species.Equals(_species[i].ID) == true)
|
|
{
|
|
SpeciesButton.SelectId(i);
|
|
}
|
|
}
|
|
|
|
// If our species isn't available then reset it to default.
|
|
if (Profile != null)
|
|
{
|
|
if (!speciesIds.Contains(Profile.Species))
|
|
{
|
|
SetSpecies(SharedHumanoidAppearanceSystem.DefaultSpecies);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RefreshAntags()
|
|
{
|
|
AntagList.DisposeAllChildren();
|
|
var items = new[]
|
|
{
|
|
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
|
("humanoid-profile-editor-antag-preference-no-button", 1)
|
|
};
|
|
|
|
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
|
|
{
|
|
if (!antag.SetPreference)
|
|
continue;
|
|
|
|
var antagContainer = new BoxContainer()
|
|
{
|
|
Orientation = LayoutOrientation.Horizontal,
|
|
};
|
|
|
|
var selector = new RequirementsSelector()
|
|
{
|
|
Margin = new Thickness(3f, 3f, 3f, 0f),
|
|
};
|
|
|
|
var title = Loc.GetString(antag.Name);
|
|
var description = Loc.GetString(antag.Objective);
|
|
selector.Setup(items, title, 250, description);
|
|
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
|
|
|
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
|
|
{
|
|
selector.LockRequirements(reason);
|
|
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
|
SetDirty();
|
|
}
|
|
else
|
|
{
|
|
selector.UnlockRequirements();
|
|
}
|
|
|
|
selector.OnSelected += preference =>
|
|
{
|
|
Profile = Profile?.WithAntagPreference(antag.ID, preference == 0);
|
|
SetDirty();
|
|
};
|
|
|
|
antagContainer.AddChild(selector);
|
|
|
|
antagContainer.AddChild(new Button()
|
|
{
|
|
Disabled = true,
|
|
Text = Loc.GetString("loadout-window"),
|
|
HorizontalAlignment = HAlignment.Right,
|
|
Margin = new Thickness(3f, 0f, 0f, 0f),
|
|
});
|
|
|
|
AntagList.AddChild(antagContainer);
|
|
}
|
|
}
|
|
|
|
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>
|
|
/// Refresh all loadouts.
|
|
/// </summary>
|
|
public void RefreshLoadouts()
|
|
{
|
|
_loadoutWindow?.Dispose();
|
|
}
|
|
|
|
/// <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()
|
|
{
|
|
_entManager.DeleteEntity(PreviewDummy);
|
|
PreviewDummy = EntityUid.Invalid;
|
|
|
|
if (Profile == null || !_prototypeManager.HasIndex<SpeciesPrototype>(Profile.Species))
|
|
return;
|
|
|
|
PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed);
|
|
SpriteView.SetEntity(PreviewDummy);
|
|
}
|
|
|
|
/// <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();
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
UpdateCMarkingsFacialHair();
|
|
|
|
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 || !_entManager.EntityExists(PreviewDummy))
|
|
return;
|
|
|
|
_entManager.System<HumanoidAppearanceSystem>().LoadProfile(PreviewDummy, Profile);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes all job selectors.
|
|
/// </summary>
|
|
public void RefreshJobs()
|
|
{
|
|
JobList.DisposeAllChildren();
|
|
_jobCategories.Clear();
|
|
_jobPriorities.Clear();
|
|
var firstCategory = true;
|
|
|
|
// Get all displayed departments
|
|
var departments = new List<DepartmentPrototype>();
|
|
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
|
|
{
|
|
if (department.EditorHidden)
|
|
continue;
|
|
|
|
departments.Add(department);
|
|
}
|
|
|
|
departments.Sort(DepartmentUIComparer.Instance);
|
|
|
|
var items = new[]
|
|
{
|
|
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
|
|
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
|
|
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
|
|
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
|
|
};
|
|
|
|
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);
|
|
|
|
foreach (var job in jobs)
|
|
{
|
|
var jobContainer = new BoxContainer()
|
|
{
|
|
Orientation = LayoutOrientation.Horizontal,
|
|
};
|
|
|
|
var selector = new RequirementsSelector()
|
|
{
|
|
Margin = new Thickness(3f, 3f, 3f, 0f),
|
|
};
|
|
|
|
var icon = new TextureRect
|
|
{
|
|
TextureScale = new Vector2(2, 2),
|
|
VerticalAlignment = VAlignment.Center
|
|
};
|
|
var jobIcon = _prototypeManager.Index(job.Icon);
|
|
icon.Texture = jobIcon.Icon.Frame0();
|
|
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
|
|
|
|
if (!_requirements.IsAllowed(job, out var reason))
|
|
{
|
|
selector.LockRequirements(reason);
|
|
}
|
|
else
|
|
{
|
|
selector.UnlockRequirements();
|
|
}
|
|
|
|
selector.OnSelected += selectedPrio =>
|
|
{
|
|
var selectedJobPrio = (JobPriority) selectedPrio;
|
|
Profile = Profile?.WithJobPriority(job.ID, selectedJobPrio);
|
|
|
|
foreach (var (jobId, other) in _jobPriorities)
|
|
{
|
|
// Sync other selectors with the same job in case of multiple department jobs
|
|
if (jobId == job.ID)
|
|
{
|
|
other.Select(selectedPrio);
|
|
continue;
|
|
}
|
|
|
|
if (selectedJobPrio != JobPriority.High || (JobPriority) other.Selected != JobPriority.High)
|
|
continue;
|
|
|
|
// Lower any other high priorities to medium.
|
|
other.Select((int)JobPriority.Medium);
|
|
Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium);
|
|
}
|
|
|
|
// TODO: Only reload on high change (either to or from).
|
|
ReloadPreview();
|
|
|
|
UpdateJobPriorities();
|
|
SetDirty();
|
|
};
|
|
|
|
var loadoutWindowBtn = new Button()
|
|
{
|
|
Text = Loc.GetString("loadout-window"),
|
|
HorizontalAlignment = HAlignment.Right,
|
|
VerticalAlignment = VAlignment.Center,
|
|
Margin = new Thickness(3f, 3f, 0f, 0f),
|
|
};
|
|
|
|
var collection = IoCManager.Instance!;
|
|
var protoManager = collection.Resolve<IPrototypeManager>();
|
|
|
|
// If no loadout found then disabled button
|
|
if (!protoManager.TryIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID), out var roleLoadoutProto))
|
|
{
|
|
loadoutWindowBtn.Disabled = true;
|
|
}
|
|
// else
|
|
else
|
|
{
|
|
loadoutWindowBtn.OnPressed += args =>
|
|
{
|
|
RoleLoadout? loadout = null;
|
|
|
|
// Clone so we don't modify the underlying loadout.
|
|
Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
|
|
loadout = loadout?.Clone();
|
|
|
|
if (loadout == null)
|
|
{
|
|
loadout = new RoleLoadout(roleLoadoutProto.ID);
|
|
loadout.SetDefault(_prototypeManager);
|
|
}
|
|
|
|
OpenLoadout(job, loadout, roleLoadoutProto);
|
|
};
|
|
}
|
|
|
|
_jobPriorities.Add((job.ID, selector));
|
|
jobContainer.AddChild(selector);
|
|
jobContainer.AddChild(loadoutWindowBtn);
|
|
category.AddChild(jobContainer);
|
|
}
|
|
}
|
|
|
|
UpdateJobPriorities();
|
|
}
|
|
|
|
private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLoadoutPrototype roleLoadoutProto)
|
|
{
|
|
_loadoutWindow?.Dispose();
|
|
_loadoutWindow = null;
|
|
var collection = IoCManager.Instance;
|
|
|
|
if (collection == null || _playerManager.LocalSession == null || Profile == null)
|
|
return;
|
|
|
|
JobOverride = jobProto;
|
|
var session = _playerManager.LocalSession;
|
|
|
|
_loadoutWindow = new LoadoutWindow(Profile, roleLoadout, roleLoadoutProto, _playerManager.LocalSession, collection)
|
|
{
|
|
Title = jobProto?.ID + "-loadout",
|
|
};
|
|
|
|
// Refresh the buttons etc.
|
|
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
|
_loadoutWindow.OpenCenteredLeft();
|
|
|
|
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
|
{
|
|
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
|
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
|
Profile = Profile?.WithLoadout(roleLoadout);
|
|
SetDirty();
|
|
ReloadPreview();
|
|
};
|
|
|
|
_loadoutWindow.OnLoadoutUnpressed += (loadoutGroup, loadoutProto) =>
|
|
{
|
|
roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
|
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
|
Profile = Profile?.WithLoadout(roleLoadout);
|
|
SetDirty();
|
|
ReloadPreview();
|
|
};
|
|
|
|
JobOverride = jobProto;
|
|
ReloadPreview();
|
|
|
|
_loadoutWindow.OnClose += () =>
|
|
{
|
|
JobOverride = null;
|
|
SetDirty();
|
|
ReloadPreview();
|
|
};
|
|
|
|
if (Profile is null)
|
|
return;
|
|
|
|
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();
|
|
ReloadProfilePreview();
|
|
}
|
|
|
|
private void OnSkinColorOnValueChanged()
|
|
{
|
|
if (Profile is null) return;
|
|
|
|
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
|
|
|
switch (skin)
|
|
{
|
|
case HumanoidSkinColor.HumanToned:
|
|
{
|
|
if (!Skin.Visible)
|
|
{
|
|
Skin.Visible = true;
|
|
RgbSkinColorContainer.Visible = false;
|
|
}
|
|
|
|
var color = SkinColor.HumanSkinTone((int) Skin.Value);
|
|
|
|
Markings.CurrentSkinColor = color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.Hues:
|
|
{
|
|
if (!RgbSkinColorContainer.Visible)
|
|
{
|
|
Skin.Visible = false;
|
|
RgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
Markings.CurrentSkinColor = _rgbSkinColorSelector.Color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.TintedHues:
|
|
{
|
|
if (!RgbSkinColorContainer.Visible)
|
|
{
|
|
Skin.Visible = false;
|
|
RgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
|
|
|
|
Markings.CurrentSkinColor = color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.VoxFeathers:
|
|
{
|
|
if (!RgbSkinColorContainer.Visible)
|
|
{
|
|
Skin.Visible = false;
|
|
RgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
var color = SkinColor.ClosestVoxColor(_rgbSkinColorSelector.Color);
|
|
|
|
Markings.CurrentSkinColor = color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetDirty();
|
|
ReloadProfilePreview();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if (!disposing)
|
|
return;
|
|
|
|
_loadoutWindow?.Dispose();
|
|
_loadoutWindow = null;
|
|
_entManager.DeleteEntity(PreviewDummy);
|
|
PreviewDummy = EntityUid.Invalid;
|
|
}
|
|
|
|
private void SetAge(int newAge)
|
|
{
|
|
Profile = Profile?.WithAge(newAge);
|
|
ReloadPreview();
|
|
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();
|
|
Markings.SetSex(newSex);
|
|
ReloadPreview();
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetGender(Gender newGender)
|
|
{
|
|
Profile = Profile?.WithGender(newGender);
|
|
ReloadPreview();
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetSpecies(string newSpecies)
|
|
{
|
|
Profile = Profile?.WithSpecies(newSpecies);
|
|
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
|
|
Markings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
|
|
// In case there's job restrictions for the species
|
|
RefreshJobs();
|
|
// In case there's species restrictions for loadouts
|
|
RefreshLoadouts();
|
|
UpdateSexControls(); // update sex for new species
|
|
UpdateSpeciesGuidebookIcon();
|
|
SetDirty();
|
|
ReloadPreview();
|
|
}
|
|
|
|
private void SetName(string newName)
|
|
{
|
|
Profile = Profile?.WithName(newName);
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
|
{
|
|
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
|
SetDirty();
|
|
}
|
|
|
|
private bool IsDirty
|
|
{
|
|
get => _isDirty;
|
|
set
|
|
{
|
|
if (_isDirty == value)
|
|
return;
|
|
|
|
_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() ?? "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates selected job priorities to the profile's.
|
|
/// </summary>
|
|
private void UpdateJobPriorities()
|
|
{
|
|
foreach (var (jobId, prioritySelector) in _jobPriorities)
|
|
{
|
|
var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
|
|
prioritySelector.Select((int) priority);
|
|
}
|
|
}
|
|
|
|
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 (!Skin.Visible)
|
|
{
|
|
Skin.Visible = true;
|
|
RgbSkinColorContainer.Visible = false;
|
|
}
|
|
|
|
Skin.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
|
|
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.Hues:
|
|
{
|
|
if (!RgbSkinColorContainer.Visible)
|
|
{
|
|
Skin.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)
|
|
{
|
|
Skin.Visible = false;
|
|
RgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
// set the RGB values to the direct values otherwise
|
|
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.VoxFeathers:
|
|
{
|
|
if (!RgbSkinColorContainer.Visible)
|
|
{
|
|
Skin.Visible = false;
|
|
RgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
_rgbSkinColorSelector.Color = SkinColor.ClosestVoxColor(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;
|
|
}
|
|
|
|
Markings.SetData(Profile.Appearance.Markings, Profile.Species,
|
|
Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
|
|
);
|
|
}
|
|
|
|
private void UpdateGenderControls()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PronounsButton.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() { new(Profile.Appearance.HairStyleId, new List<Color>() { Profile.Appearance.HairColor }) },
|
|
};
|
|
|
|
var facialHairMarking = Profile.Appearance.FacialHairStyleId switch
|
|
{
|
|
HairStyles.DefaultFacialHairStyle => new List<Marking>(),
|
|
_ => new() { new(Profile.Appearance.FacialHairStyleId, new List<Color>() { Profile.Appearance.FacialHairColor }) },
|
|
};
|
|
|
|
HairStylePicker.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)
|
|
{
|
|
Markings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color>() { hairColor.Value });
|
|
}
|
|
else
|
|
{
|
|
Markings.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)
|
|
{
|
|
Markings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color>() { facialHairColor.Value });
|
|
}
|
|
else
|
|
{
|
|
Markings.FacialHairMarking = null;
|
|
}
|
|
}
|
|
|
|
private void UpdateEyePickers()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Markings.CurrentEyeColor = Profile.Appearance.EyeColor;
|
|
EyeColorPicker.SetData(Profile.Appearance.EyeColor);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private void RandomizeEverything()
|
|
{
|
|
Profile = HumanoidCharacterProfile.Random();
|
|
SetProfile(Profile, CharacterSlot);
|
|
SetDirty();
|
|
}
|
|
|
|
private void RandomizeName()
|
|
{
|
|
if (Profile == null) return;
|
|
var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
|
|
SetName(name);
|
|
UpdateNameEdit();
|
|
}
|
|
|
|
private async void ImportProfile()
|
|
{
|
|
if (_exporting || CharacterSlot == null || Profile == null)
|
|
return;
|
|
|
|
StartExport();
|
|
await using var file = await _dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
|
|
|
|
if (file == null)
|
|
{
|
|
EndExport();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var profile = _entManager.System<HumanoidAppearanceSystem>().FromStream(file, _playerManager.LocalSession!);
|
|
var oldProfile = Profile;
|
|
SetProfile(profile, CharacterSlot);
|
|
|
|
IsDirty = !profile.MemberwiseEquals(oldProfile);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
_sawmill.Error($"Error when importing profile\n{exc.StackTrace}");
|
|
}
|
|
finally
|
|
{
|
|
EndExport();
|
|
}
|
|
}
|
|
|
|
private async void ExportProfile()
|
|
{
|
|
if (Profile == null || _exporting)
|
|
return;
|
|
|
|
StartExport();
|
|
var file = await _dialogManager.SaveFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
|
|
|
|
if (file == null)
|
|
{
|
|
EndExport();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var dataNode = _entManager.System<HumanoidAppearanceSystem>().ToDataNode(Profile);
|
|
await using var writer = new StreamWriter(file.Value.fileStream);
|
|
dataNode.Write(writer);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
_sawmill.Error($"Error when exporting profile\n{exc.StackTrace}");
|
|
}
|
|
finally
|
|
{
|
|
EndExport();
|
|
await file.Value.fileStream.DisposeAsync();
|
|
}
|
|
}
|
|
|
|
private void StartExport()
|
|
{
|
|
_exporting = true;
|
|
ImportButton.Disabled = true;
|
|
ExportButton.Disabled = true;
|
|
}
|
|
|
|
private void EndExport()
|
|
{
|
|
_exporting = false;
|
|
ImportButton.Disabled = false;
|
|
ExportButton.Disabled = false;
|
|
}
|
|
}
|
|
}
|