* LobbyUI fixes I have no idea which were bugs prior but anyway fix stuff. * More fixes * Test moment
1209 lines
41 KiB
C#
1209 lines
41 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.Guidebook;
|
|
using Content.Client.Humanoid;
|
|
using Content.Client.Lobby;
|
|
using Content.Client.Message;
|
|
using Content.Client.Players.PlayTimeTracking;
|
|
using Content.Client.Stylesheets;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Client.UserInterface.Systems.Guidebook;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.Clothing;
|
|
using Content.Shared.GameTicking;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Humanoid.Markings;
|
|
using Content.Shared.Humanoid.Prototypes;
|
|
using Content.Shared.Preferences;
|
|
using Content.Shared.Preferences.Loadouts;
|
|
using Content.Shared.Preferences.Loadouts.Effects;
|
|
using Content.Shared.Roles;
|
|
using Content.Shared.Traits;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Client.Utility;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
using Direction = Robust.Shared.Maths.Direction;
|
|
|
|
namespace Content.Client.Preferences.UI
|
|
{
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class HumanoidProfileEditor : BoxContainer
|
|
{
|
|
private readonly IClientPreferencesManager _preferencesManager;
|
|
private readonly IPrototypeManager _prototypeManager;
|
|
private readonly MarkingManager _markingManager;
|
|
private readonly JobRequirementsManager _requirements;
|
|
|
|
private LineEdit _ageEdit => CAgeEdit;
|
|
private LineEdit _nameEdit => CNameEdit;
|
|
private TextEdit? _flavorTextEdit;
|
|
private Button _nameRandomButton => CNameRandomize;
|
|
private Button _randomizeEverythingButton => CRandomizeEverything;
|
|
private RichTextLabel _warningLabel => CWarningLabel;
|
|
private Button _saveButton => CSaveButton;
|
|
private OptionButton _sexButton => CSexButton;
|
|
private OptionButton _genderButton => CPronounsButton;
|
|
private Slider _skinColor => CSkin;
|
|
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
|
|
private SingleMarkingPicker _hairPicker => CHairStylePicker;
|
|
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
|
|
private EyeColorPicker _eyesPicker => CEyeColorPicker;
|
|
|
|
private TabContainer _tabContainer => CTabContainer;
|
|
private BoxContainer _jobList => CJobList;
|
|
private BoxContainer _antagList => CAntagList;
|
|
private BoxContainer _traitsList => CTraitsList;
|
|
private readonly List<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 SpriteView _previewSpriteView => CSpriteView;
|
|
private Button _previewRotateLeftButton => CSpriteRotateLeft;
|
|
private Button _previewRotateRightButton => CSpriteRotateRight;
|
|
private Direction _previewRotation = Direction.North;
|
|
|
|
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
|
|
private ColorSelectorSliders _rgbSkinColorSelector;
|
|
|
|
private bool _isDirty;
|
|
public int CharacterSlot;
|
|
public HumanoidCharacterProfile? Profile;
|
|
|
|
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
|
|
|
|
[ValidatePrototypeId<GuideEntryPrototype>]
|
|
private const string DefaultSpeciesGuidebook = "Species";
|
|
|
|
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
_prototypeManager = prototypeManager;
|
|
_preferencesManager = preferencesManager;
|
|
_markingManager = IoCManager.Resolve<MarkingManager>();
|
|
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
controller.PreviewDummyUpdated += OnDummyUpdate;
|
|
|
|
_previewSpriteView.SetEntity(controller.GetPreviewDummy());
|
|
|
|
#region Left
|
|
|
|
#region Name
|
|
|
|
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
|
|
_nameRandomButton.OnPressed += args => RandomizeName();
|
|
_randomizeEverythingButton.OnPressed += args => { RandomizeEverything(); };
|
|
_warningLabel.SetMarkup($"[color=red]{Loc.GetString("humanoid-profile-editor-naming-rules-warning")}[/color]");
|
|
|
|
#endregion Name
|
|
|
|
#region Appearance
|
|
|
|
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
|
|
|
|
#region Sex
|
|
|
|
_sexButton.OnItemSelected += args =>
|
|
{
|
|
_sexButton.SelectId(args.Id);
|
|
SetSex((Sex) args.Id);
|
|
};
|
|
|
|
#endregion Sex
|
|
|
|
#region Age
|
|
|
|
_ageEdit.OnTextChanged += args =>
|
|
{
|
|
if (!int.TryParse(args.Text, out var newAge))
|
|
return;
|
|
SetAge(newAge);
|
|
};
|
|
|
|
#endregion Age
|
|
|
|
#region Gender
|
|
|
|
_genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-male-text"), (int) Gender.Male);
|
|
_genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-female-text"), (int) Gender.Female);
|
|
_genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), (int) Gender.Epicene);
|
|
_genderButton.AddItem(Loc.GetString("humanoid-profile-editor-pronouns-neuter-text"), (int) Gender.Neuter);
|
|
|
|
_genderButton.OnItemSelected += args =>
|
|
{
|
|
_genderButton.SelectId(args.Id);
|
|
SetGender((Gender) args.Id);
|
|
};
|
|
|
|
#endregion Gender
|
|
|
|
#region Species
|
|
|
|
_speciesList = prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart).ToList();
|
|
for (var i = 0; i < _speciesList.Count; i++)
|
|
{
|
|
var name = Loc.GetString(_speciesList[i].Name);
|
|
CSpeciesButton.AddItem(name, i);
|
|
}
|
|
|
|
CSpeciesButton.OnItemSelected += args =>
|
|
{
|
|
CSpeciesButton.SelectId(args.Id);
|
|
SetSpecies(_speciesList[args.Id].ID);
|
|
UpdateHairPickers();
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
#endregion Species
|
|
|
|
#region Skin
|
|
|
|
|
|
_skinColor.OnValueChanged += _ =>
|
|
{
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
_rgbSkinColorContainer.AddChild(_rgbSkinColorSelector = new ColorSelectorSliders());
|
|
_rgbSkinColorSelector.OnColorChanged += _ =>
|
|
{
|
|
OnSkinColorOnValueChanged();
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region Hair
|
|
|
|
_hairPicker.OnMarkingSelect += newStyle =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(newStyle.id));
|
|
SetDirty();
|
|
};
|
|
|
|
_hairPicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsHair();
|
|
SetDirty();
|
|
};
|
|
|
|
_facialHairPicker.OnMarkingSelect += newStyle =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
|
|
SetDirty();
|
|
};
|
|
|
|
_facialHairPicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsFacialHair();
|
|
SetDirty();
|
|
};
|
|
|
|
_hairPicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
SetDirty();
|
|
};
|
|
|
|
_facialHairPicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsFacialHair();
|
|
SetDirty();
|
|
};
|
|
|
|
_hairPicker.OnSlotAdd += delegate()
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, Profile.Species).Keys
|
|
.FirstOrDefault();
|
|
|
|
if (string.IsNullOrEmpty(hair))
|
|
return;
|
|
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(hair)
|
|
);
|
|
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
SetDirty();
|
|
};
|
|
|
|
_facialHairPicker.OnSlotAdd += delegate()
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
var hair = _markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, Profile.Species).Keys
|
|
.FirstOrDefault();
|
|
|
|
if (string.IsNullOrEmpty(hair))
|
|
return;
|
|
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(hair)
|
|
);
|
|
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsFacialHair();
|
|
SetDirty();
|
|
};
|
|
|
|
#endregion Hair
|
|
|
|
#region SpawnPriority
|
|
|
|
foreach (var value in Enum.GetValues<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
|
|
|
|
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
|
|
|
|
#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
|
|
|
|
#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();
|
|
|
|
IsDirty = false;
|
|
controller.UpdateProfile();
|
|
}
|
|
|
|
private void SetDirty()
|
|
{
|
|
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
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();
|
|
|
|
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
|
|
{
|
|
if (!antag.SetPreference)
|
|
continue;
|
|
|
|
var selector = new AntagPreferenceSelector(antag, btnGroup)
|
|
{
|
|
Margin = new Thickness(3f, 3f, 3f, 0f),
|
|
};
|
|
_antagList.AddChild(selector);
|
|
_antagPreferences.Add(selector);
|
|
if (selector.Disabled)
|
|
{
|
|
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
|
SetDirty();
|
|
}
|
|
|
|
selector.PreferenceChanged += preference =>
|
|
{
|
|
Profile = Profile?.WithAntagPreference(antag.ID, preference);
|
|
SetDirty();
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
private void UpdateRoleRequirements()
|
|
{
|
|
_jobList.DisposeAllChildren();
|
|
_jobPriorities.Clear();
|
|
_jobCategories.Clear();
|
|
var firstCategory = true;
|
|
|
|
var departments = _prototypeManager.EnumeratePrototypes<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("#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<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)
|
|
{
|
|
Margin = new Thickness(3f, 3f, 3f, 0f),
|
|
};
|
|
|
|
if (!_requirements.IsAllowed(job, out var reason))
|
|
{
|
|
selector.LockRequirements(reason);
|
|
}
|
|
|
|
category.AddChild(selector);
|
|
_jobPriorities.Add(selector);
|
|
|
|
selector.LoadoutUpdated += args =>
|
|
{
|
|
Profile = Profile?.WithLoadout(args);
|
|
SetDirty();
|
|
};
|
|
|
|
selector.PriorityChanged += priority =>
|
|
{
|
|
Profile = Profile?.WithJobPriority(job.ID, priority);
|
|
|
|
foreach (var jobSelector in _jobPriorities)
|
|
{
|
|
// Sync other selectors with the same job in case of multiple department jobs
|
|
if (jobSelector.Proto == selector.Proto)
|
|
{
|
|
jobSelector.Priority = priority;
|
|
}
|
|
else if (priority == JobPriority.High && jobSelector.Priority == JobPriority.High)
|
|
{
|
|
// Lower any other high priorities to medium.
|
|
jobSelector.Priority = JobPriority.Medium;
|
|
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
|
|
}
|
|
}
|
|
|
|
SetDirty();
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
if (Profile is not null)
|
|
{
|
|
UpdateJobPriorities();
|
|
}
|
|
}
|
|
|
|
private void OnFlavorTextChange(string content)
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
Profile = Profile.WithFlavorText(content);
|
|
SetDirty();
|
|
}
|
|
|
|
private void OnMarkingChange(MarkingSet markings)
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
|
|
IsDirty = true;
|
|
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
controller.UpdateProfile(Profile);
|
|
controller.ReloadProfile();
|
|
}
|
|
|
|
private void OnSkinColorOnValueChanged()
|
|
{
|
|
if (Profile is null) return;
|
|
|
|
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
|
|
|
switch (skin)
|
|
{
|
|
case HumanoidSkinColor.HumanToned:
|
|
{
|
|
if (!_skinColor.Visible)
|
|
{
|
|
_skinColor.Visible = true;
|
|
_rgbSkinColorContainer.Visible = false;
|
|
}
|
|
|
|
var color = SkinColor.HumanSkinTone((int) _skinColor.Value);
|
|
|
|
CMarkings.CurrentSkinColor = color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.Hues:
|
|
{
|
|
if (!_rgbSkinColorContainer.Visible)
|
|
{
|
|
_skinColor.Visible = false;
|
|
_rgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
CMarkings.CurrentSkinColor = _rgbSkinColorSelector.Color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
|
|
break;
|
|
}
|
|
case HumanoidSkinColor.TintedHues:
|
|
{
|
|
if (!_rgbSkinColorContainer.Visible)
|
|
{
|
|
_skinColor.Visible = false;
|
|
_rgbSkinColorContainer.Visible = true;
|
|
}
|
|
|
|
var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
|
|
|
|
CMarkings.CurrentSkinColor = color;
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
|
break;
|
|
}
|
|
}
|
|
|
|
IsDirty = true;
|
|
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
controller.UpdateProfile(Profile);
|
|
controller.ReloadProfile();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if (!disposing)
|
|
return;
|
|
|
|
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
|
controller.PreviewDummyUpdated -= OnDummyUpdate;
|
|
_requirements.Updated -= UpdateAntagRequirements;
|
|
_requirements.Updated -= UpdateRoleRequirements;
|
|
_preferencesManager.OnServerDataLoaded -= LoadServerData;
|
|
}
|
|
|
|
public void LoadServerData()
|
|
{
|
|
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
|
|
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
|
|
|
|
UpdateAntagRequirements();
|
|
UpdateRoleRequirements();
|
|
UpdateControls();
|
|
ShowClothes.Pressed = true;
|
|
}
|
|
|
|
private void SetAge(int newAge)
|
|
{
|
|
Profile = Profile?.WithAge(newAge);
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetSex(Sex newSex)
|
|
{
|
|
Profile = Profile?.WithSex(newSex);
|
|
// for convenience, default to most common gender when new sex is selected
|
|
switch (newSex)
|
|
{
|
|
case Sex.Male:
|
|
Profile = Profile?.WithGender(Gender.Male);
|
|
break;
|
|
case Sex.Female:
|
|
Profile = Profile?.WithGender(Gender.Female);
|
|
break;
|
|
default:
|
|
Profile = Profile?.WithGender(Gender.Epicene);
|
|
break;
|
|
}
|
|
UpdateGenderControls();
|
|
CMarkings.SetSex(newSex);
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetGender(Gender newGender)
|
|
{
|
|
Profile = Profile?.WithGender(newGender);
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetSpecies(string newSpecies)
|
|
{
|
|
Profile = Profile?.WithSpecies(newSpecies);
|
|
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
|
|
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
|
|
UpdateSexControls(); // update sex for new species
|
|
UpdateSpeciesGuidebookIcon();
|
|
SetDirty();
|
|
UpdatePreview();
|
|
}
|
|
|
|
private void SetName(string newName)
|
|
{
|
|
Profile = Profile?.WithName(newName);
|
|
SetDirty();
|
|
}
|
|
|
|
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
|
{
|
|
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
|
SetDirty();
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
IsDirty = false;
|
|
|
|
if (Profile == null)
|
|
return;
|
|
|
|
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
|
|
OnProfileChanged?.Invoke(Profile, CharacterSlot);
|
|
// Reset profile to default.
|
|
UserInterfaceManager.GetUIController<LobbyUIController>().UpdateProfile();
|
|
}
|
|
|
|
private bool IsDirty
|
|
{
|
|
get => _isDirty;
|
|
set
|
|
{
|
|
_isDirty = value;
|
|
UpdateSaveButton();
|
|
}
|
|
}
|
|
|
|
private void UpdateNameEdit()
|
|
{
|
|
_nameEdit.Text = Profile?.Name ?? "";
|
|
}
|
|
|
|
private void UpdateFlavorTextEdit()
|
|
{
|
|
if(_flavorTextEdit != null)
|
|
{
|
|
_flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
|
|
}
|
|
}
|
|
|
|
private void UpdateAgeEdit()
|
|
{
|
|
_ageEdit.Text = Profile?.Age.ToString() ?? "";
|
|
}
|
|
|
|
private void UpdateSexControls()
|
|
{
|
|
if (Profile == null)
|
|
return;
|
|
|
|
_sexButton.Clear();
|
|
|
|
var sexes = new List<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.Appearance.SkinColor, Profile.Appearance.EyeColor
|
|
);
|
|
}
|
|
|
|
private void UpdateSpecies()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species));
|
|
}
|
|
|
|
private void UpdateGenderControls()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_genderButton.SelectId((int) Profile.Gender);
|
|
}
|
|
|
|
private void UpdateSpawnPriorityControls()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
|
|
}
|
|
|
|
private void UpdateHairPickers()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
var hairMarking = Profile.Appearance.HairStyleId switch
|
|
{
|
|
HairStyles.DefaultHairStyle => new List<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 }) },
|
|
};
|
|
|
|
_hairPicker.UpdateData(
|
|
hairMarking,
|
|
Profile.Species,
|
|
1);
|
|
_facialHairPicker.UpdateData(
|
|
facialHairMarking,
|
|
Profile.Species,
|
|
1);
|
|
}
|
|
|
|
private void UpdateCMarkingsHair()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// hair color
|
|
Color? hairColor = null;
|
|
if ( Profile.Appearance.HairStyleId != HairStyles.DefaultHairStyle &&
|
|
_markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto)
|
|
)
|
|
{
|
|
if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, hairProto, _prototypeManager))
|
|
{
|
|
if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
|
|
{
|
|
hairColor = Profile.Appearance.SkinColor;
|
|
}
|
|
else
|
|
{
|
|
hairColor = Profile.Appearance.HairColor;
|
|
}
|
|
}
|
|
}
|
|
if (hairColor != null)
|
|
{
|
|
CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List<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.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
|
|
{
|
|
facialHairColor = Profile.Appearance.SkinColor;
|
|
}
|
|
else
|
|
{
|
|
facialHairColor = Profile.Appearance.FacialHairColor;
|
|
}
|
|
}
|
|
}
|
|
if (facialHairColor != null)
|
|
{
|
|
CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<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;
|
|
UpdateNameEdit();
|
|
UpdateFlavorTextEdit();
|
|
UpdateSexControls();
|
|
UpdateGenderControls();
|
|
UpdateSkinColor();
|
|
UpdateSpecies();
|
|
UpdateSpawnPriorityControls();
|
|
UpdateAgeEdit();
|
|
UpdateEyePickers();
|
|
UpdateSaveButton();
|
|
UpdateLoadouts();
|
|
UpdateJobPriorities();
|
|
UpdateAntagPreferences();
|
|
UpdateTraitPreferences();
|
|
UpdateMarkings();
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
UpdateCMarkingsFacialHair();
|
|
|
|
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
|
|
}
|
|
|
|
private void UpdateJobPriorities()
|
|
{
|
|
foreach (var prioritySelector in _jobPriorities)
|
|
{
|
|
var jobId = prioritySelector.Proto.ID;
|
|
|
|
var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
|
|
|
|
prioritySelector.Priority = priority;
|
|
}
|
|
}
|
|
|
|
private void UpdateLoadouts()
|
|
{
|
|
foreach (var prioritySelector in _jobPriorities)
|
|
{
|
|
prioritySelector.CloseLoadout();
|
|
}
|
|
}
|
|
|
|
private void UpdateAntagPreferences()
|
|
{
|
|
foreach (var preferenceSelector in _antagPreferences)
|
|
{
|
|
var antagId = preferenceSelector.Proto.ID;
|
|
var preference = Profile?.AntagPreferences.Contains(antagId) ?? false;
|
|
preferenceSelector.Preference = preference;
|
|
}
|
|
}
|
|
|
|
private void UpdateTraitPreferences()
|
|
{
|
|
foreach (var preferenceSelector in _traitPreferences)
|
|
{
|
|
var traitId = preferenceSelector.Trait.ID;
|
|
var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
|
|
|
|
preferenceSelector.Preference = preference;
|
|
}
|
|
}
|
|
|
|
private sealed class TraitPreferenceSelector : Control
|
|
{
|
|
public TraitPrototype Trait { get; }
|
|
private readonly CheckBox _checkBox;
|
|
|
|
public bool Preference
|
|
{
|
|
get => _checkBox.Pressed;
|
|
set => _checkBox.Pressed = value;
|
|
}
|
|
|
|
public event Action<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);
|
|
}
|
|
}
|
|
}
|
|
}
|