mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Split HumanoidProfileEditor.xaml.cs into separate files (#42715)
* separate humanoid profile editor into different files * move this to the rest of the fields
This commit is contained in:
299
Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs
Normal file
299
Content.Client/Lobby/UI/HumanoidProfileEditor.Appearance.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
private ColorSelectorSliders _rgbSkinColorSelector;
|
||||
private List<SpeciesPrototype> _species = new();
|
||||
private static readonly ProtoId<GuideEntryPrototype> DefaultSpeciesGuidebook = "Species";
|
||||
|
||||
public void UpdateSpeciesGuidebookIcon()
|
||||
{
|
||||
SpeciesInfoButton.StyleClasses.Clear();
|
||||
|
||||
var species = Profile?.Species;
|
||||
if (species is null)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.Resolve<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.StyleIdentifier = style;
|
||||
}
|
||||
|
||||
private void UpdateGenderControls()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PronounsButton.SelectId((int)Profile.Gender);
|
||||
}
|
||||
|
||||
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.Resolve(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 UpdateEyePickers()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_markingsModel.SetOrganEyeColor(Profile.Appearance.EyeColor);
|
||||
EyeColorPicker.SetData(Profile.Appearance.EyeColor);
|
||||
}
|
||||
|
||||
private void UpdateSkinColor()
|
||||
{
|
||||
if (Profile == null)
|
||||
return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
Skin.Visible = true;
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
Skin.Value = strategy.ToUnary(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
_rgbSkinColorSelector.Color = strategy.ClosestSkinColor(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpawnPriorityControls()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SpawnPriorityButton.SelectId((int)Profile.SpawnPriority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the species selector.
|
||||
/// </summary>
|
||||
public void RefreshSpecies()
|
||||
{
|
||||
SpeciesButton.Clear();
|
||||
_species.Clear();
|
||||
|
||||
_species.AddRange(_prototypeManager.EnumeratePrototypes<SpeciesPrototype>().Where(o => o.RoundStart));
|
||||
_species.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase));
|
||||
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(HumanoidCharacterProfile.DefaultSpecies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSpecies(string newSpecies)
|
||||
{
|
||||
Profile = Profile?.WithSpecies(newSpecies);
|
||||
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
|
||||
_markingsModel.OrganData = _markingManager.GetMarkingData(newSpecies);
|
||||
_markingsModel.ValidateMarkings();
|
||||
// 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();
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
private void SetAge(int newAge)
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
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();
|
||||
_markingsModel.SetOrganSexes(newSex);
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
private void SetGender(Gender newGender)
|
||||
{
|
||||
Profile = Profile?.WithGender(newGender);
|
||||
ReloadPreview();
|
||||
}
|
||||
|
||||
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
||||
{
|
||||
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// TODO GUIDEBOOK
|
||||
// make the species guide book a field on the species prototype.
|
||||
// I.e., do what jobs/antags do.
|
||||
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var species = Profile?.Species ?? HumanoidCharacterProfile.DefaultSpecies;
|
||||
var page = DefaultSpeciesGuidebook;
|
||||
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
page = new ProtoId<GuideEntryPrototype>(species.Id); // Gross. See above todo comment.
|
||||
|
||||
if (_prototypeManager.Resolve(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
{
|
||||
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
//TODO: Don't close the guidebook if its already open, just go to the correct page
|
||||
guidebookController.OpenGuidebook(dict, includeChildren: true, selected: page);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSkinColorOnValueChanged()
|
||||
{
|
||||
if (Profile is null) return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
Skin.Visible = true;
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
var color = strategy.FromUnary(Skin.Value);
|
||||
|
||||
_markingsModel.SetOrganSkinColor(color);
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = strategy.ClosestSkinColor(_rgbSkinColorSelector.Color);
|
||||
|
||||
_markingsModel.SetOrganSkinColor(color);
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReloadProfilePreview();
|
||||
}
|
||||
}
|
||||
38
Content.Client/Lobby/UI/HumanoidProfileEditor.BasicInfo.cs
Normal file
38
Content.Client/Lobby/UI/HumanoidProfileEditor.BasicInfo.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
using Content.Shared.Preferences;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private void SetName(string newName)
|
||||
{
|
||||
Profile = Profile?.WithName(newName);
|
||||
SetDirty();
|
||||
|
||||
if (!IsDirty)
|
||||
return;
|
||||
|
||||
SpriteView.SetName(newName);
|
||||
}
|
||||
|
||||
private void UpdateNameEdit()
|
||||
{
|
||||
NameEdit.Text = Profile?.Name ?? "";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
102
Content.Client/Lobby/UI/HumanoidProfileEditor.Exports.cs
Normal file
102
Content.Client/Lobby/UI/HumanoidProfileEditor.Exports.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.IO;
|
||||
using Content.Client.Sprite;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private bool _exporting;
|
||||
private bool _imaging;
|
||||
|
||||
private async void ExportImage()
|
||||
{
|
||||
if (_imaging)
|
||||
return;
|
||||
|
||||
var dir = SpriteView.OverrideDirection ?? Direction.South;
|
||||
|
||||
// I tried disabling the button but it looks sorta goofy as it only takes a frame or two to save
|
||||
_imaging = true;
|
||||
await _entManager.System<ContentSpriteSystem>().Export(SpriteView.PreviewDummy, dir, includeId: false);
|
||||
_imaging = false;
|
||||
}
|
||||
|
||||
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")), FileAccess.Read);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
EndExport();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profile = HumanoidCharacterProfile.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 = Profile.ToDataNode();
|
||||
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;
|
||||
}
|
||||
}
|
||||
60
Content.Client/Lobby/UI/HumanoidProfileEditor.FlavorText.cs
Normal file
60
Content.Client/Lobby/UI/HumanoidProfileEditor.FlavorText.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private bool _allowFlavorText;
|
||||
|
||||
private FlavorText.FlavorText? _flavorText;
|
||||
private TextEdit? _flavorTextEdit;
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the flavor text editor status.
|
||||
/// </summary>
|
||||
public void RefreshFlavorText()
|
||||
{
|
||||
if (_allowFlavorText)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFlavorTextChange(string content)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithFlavorText(content);
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void UpdateFlavorTextEdit()
|
||||
{
|
||||
if (_flavorTextEdit != null)
|
||||
{
|
||||
_flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Content.Client/Lobby/UI/HumanoidProfileEditor.Markings.cs
Normal file
26
Content.Client/Lobby/UI/HumanoidProfileEditor.Markings.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private void UpdateMarkings()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_markingsModel.OrganData = _markingManager.GetMarkingData(Profile.Species);
|
||||
_markingsModel.OrganProfileData = _markingManager.GetProfileData(Profile.Species, Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor);
|
||||
_markingsModel.Markings = Profile.Appearance.Markings;
|
||||
}
|
||||
|
||||
private void OnMarkingChange()
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(_markingsModel.Markings));
|
||||
ReloadProfilePreview();
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
360
Content.Client/Lobby/UI/HumanoidProfileEditor.Roles.cs
Normal file
360
Content.Client/Lobby/UI/HumanoidProfileEditor.Roles.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Lobby.UI.Loadouts;
|
||||
using Content.Client.Lobby.UI.Roles;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Temporary override of their selected job, used to preview roles.
|
||||
/// </summary>
|
||||
public JobPrototype? JobOverride;
|
||||
|
||||
// One at a time.
|
||||
private LoadoutWindow? _loadoutWindow;
|
||||
|
||||
private List<(string, RequirementsSelector)> _jobPriorities = new();
|
||||
|
||||
private readonly Dictionary<string, BoxContainer> _jobCategories;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh all loadouts.
|
||||
/// </summary>
|
||||
public void RefreshLoadouts()
|
||||
{
|
||||
_loadoutWindow?.Dispose();
|
||||
}
|
||||
|
||||
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 = Loc.GetString("loadout-window-title-loadout", ("job", $"{jobProto?.LocalizedName}")),
|
||||
};
|
||||
|
||||
// Refresh the buttons etc.
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
_loadoutWindow.OpenCenteredLeft();
|
||||
|
||||
_loadoutWindow.OnNameChanged += name =>
|
||||
{
|
||||
roleLoadout.EntityName = name;
|
||||
Profile = Profile.WithLoadout(roleLoadout);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
||||
{
|
||||
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
Profile = Profile?.WithLoadout(roleLoadout);
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
_loadoutWindow.OnLoadoutUnpressed += (loadoutGroup, loadoutProto) =>
|
||||
{
|
||||
roleLoadout.RemoveLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||
Profile = Profile?.WithLoadout(roleLoadout);
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
JobOverride = jobProto;
|
||||
ReloadPreview();
|
||||
|
||||
_loadoutWindow.OnClose += () =>
|
||||
{
|
||||
JobOverride = null;
|
||||
ReloadPreview();
|
||||
};
|
||||
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
UpdateJobPriorities();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes all job selectors.
|
||||
/// </summary>
|
||||
public void RefreshJobs()
|
||||
{
|
||||
JobList.RemoveAllChildren();
|
||||
_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.Name);
|
||||
|
||||
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),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var icon = new TextureRect
|
||||
{
|
||||
TextureScale = new Vector2(2, 2),
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
var jobIcon = _prototypeManager.Index(job.Icon);
|
||||
icon.Texture = _sprite.Frame0(jobIcon.Icon);
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
||||
|
||||
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, 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(Profile, _playerManager.LocalSession, _prototypeManager);
|
||||
}
|
||||
|
||||
OpenLoadout(job, loadout, roleLoadoutProto);
|
||||
};
|
||||
}
|
||||
|
||||
_jobPriorities.Add((job.ID, selector));
|
||||
jobContainer.AddChild(selector);
|
||||
jobContainer.AddChild(loadoutWindowBtn);
|
||||
category.AddChild(jobContainer);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateJobPriorities();
|
||||
}
|
||||
|
||||
public void RefreshAntags()
|
||||
{
|
||||
AntagList.RemoveAllChildren();
|
||||
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),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var title = Loc.GetString(antag.Name);
|
||||
var description = Loc.GetString(antag.Objective);
|
||||
selector.Setup(items, title, 250, description, guides: antag.Guides);
|
||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||
|
||||
if (!_requirements.IsAllowed(
|
||||
antag,
|
||||
(HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Content.Client/Lobby/UI/HumanoidProfileEditor.Traits.cs
Normal file
124
Content.Client/Lobby/UI/HumanoidProfileEditor.Traits.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Lobby.UI.Roles;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Traits;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes traits selector
|
||||
/// </summary>
|
||||
public void RefreshTraits()
|
||||
{
|
||||
TraitsList.RemoveAllChildren();
|
||||
|
||||
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 < 1)
|
||||
{
|
||||
TraitsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("humanoid-profile-editor-no-traits"),
|
||||
FontColorOverride = Color.Gray,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup model
|
||||
Dictionary<string, List<string>> traitGroups = new();
|
||||
List<string> defaultTraits = new();
|
||||
traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits);
|
||||
|
||||
foreach (var trait in traits)
|
||||
{
|
||||
if (trait.Category == null)
|
||||
{
|
||||
defaultTraits.Add(trait.ID);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.HasIndex(trait.Category))
|
||||
continue;
|
||||
|
||||
var group = traitGroups.GetOrNew(trait.Category);
|
||||
group.Add(trait.ID);
|
||||
}
|
||||
|
||||
// Create UI view from model
|
||||
foreach (var (categoryId, categoryTraits) in traitGroups)
|
||||
{
|
||||
TraitCategoryPrototype? category = null;
|
||||
|
||||
if (categoryId != TraitCategoryPrototype.Default)
|
||||
{
|
||||
category = _prototypeManager.Index<TraitCategoryPrototype>(categoryId);
|
||||
// Label
|
||||
TraitsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString(category.Name),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
StyleClasses = { StyleClass.LabelHeading },
|
||||
});
|
||||
}
|
||||
|
||||
List<TraitPreferenceSelector?> selectors = new();
|
||||
var selectionCount = 0;
|
||||
|
||||
foreach (var traitProto in categoryTraits)
|
||||
{
|
||||
var trait = _prototypeManager.Index<TraitPrototype>(traitProto);
|
||||
var selector = new TraitPreferenceSelector(trait);
|
||||
|
||||
selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
|
||||
if (selector.Preference)
|
||||
selectionCount += trait.Cost;
|
||||
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
if (preference)
|
||||
{
|
||||
Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager);
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
|
||||
};
|
||||
selectors.Add(selector);
|
||||
}
|
||||
|
||||
// Selection counter
|
||||
if (category is { MaxTraitPoints: >= 0 })
|
||||
{
|
||||
TraitsList.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount), ("max", category.MaxTraitPoints)),
|
||||
FontColorOverride = Color.Gray
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var selector in selectors)
|
||||
{
|
||||
if (selector == null)
|
||||
continue;
|
||||
|
||||
if (category is { MaxTraitPoints: >= 0 } &&
|
||||
selector.Cost + selectionCount > category.MaxTraitPoints)
|
||||
{
|
||||
selector.Checkbox.Label.FontColorOverride = Color.Red;
|
||||
}
|
||||
|
||||
TraitsList.AddChild(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user