* Make department / job list sorting consistent. This makes late join, crew manifest and character profile all apply consistent sorting for jobs and departments. We use the already-defined weights for departments (so command, then sec, then station specific, then just sort by prototype ID). Jobs also use weight (so heads are always at the top) then prototype ID, then character name (for manifest). Removed the crewmanifest.ordering CVar as doing it via prototype weight is just easier, and that CVar was already a mess anyways. * Fix jittery job icons in lists. They were set to KeepCentered in TextureRect. This has issues because the allocated space is actually an odd number of pixels, so it tries to position the draw at a half pixel offset. Now, yes, fixing this in TextureRect would make much more sense, but get off my back. (Ok seriously we need better helper functions for doing that in the engine. Don't wanna deal with that right now and I already have this patch made.) Instead I'm just gonna fix the issue by using VerticalAlignment in all these places instead which ends up doing exactly the same thing YIPPEE. Also gave a margin next to the icon on the crew manifest. Margins people!
1417 lines
48 KiB
C#
1417 lines
48 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.Humanoid;
|
|
using Content.Client.Lobby.UI;
|
|
using Content.Client.Message;
|
|
using Content.Client.Players.PlayTimeTracking;
|
|
using Content.Client.Stylesheets;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.GameTicking;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Humanoid.Markings;
|
|
using Content.Shared.Humanoid.Prototypes;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Preferences;
|
|
using Content.Shared.Roles;
|
|
using Content.Shared.StatusIcon;
|
|
using Content.Shared.Traits;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.CustomControls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Client.Utility;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
|
using Direction = Robust.Shared.Maths.Direction;
|
|
|
|
namespace Content.Client.Preferences.UI
|
|
{
|
|
public sealed class HighlightedContainer : PanelContainer
|
|
{
|
|
public HighlightedContainer()
|
|
{
|
|
PanelOverride = new StyleBoxFlat()
|
|
{
|
|
BackgroundColor = new Color(47, 47, 53),
|
|
ContentMarginTopOverride = 10,
|
|
ContentMarginBottomOverride = 10,
|
|
ContentMarginLeftOverride = 10,
|
|
ContentMarginRightOverride = 10
|
|
};
|
|
}
|
|
}
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class HumanoidProfileEditor : Control
|
|
{
|
|
private readonly IClientPreferencesManager _preferencesManager;
|
|
private readonly IEntityManager _entMan;
|
|
private readonly IConfigurationManager _configurationManager;
|
|
private readonly MarkingManager _markingManager;
|
|
private readonly JobRequirementsManager _requirements;
|
|
|
|
private LineEdit _ageEdit => CAgeEdit;
|
|
private LineEdit _nameEdit => CNameEdit;
|
|
private TextEdit _flavorTextEdit = null!;
|
|
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 _clothingButton => CClothingButton;
|
|
private OptionButton _backpackButton => CBackpackButton;
|
|
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;
|
|
private readonly List<TraitPreferenceSelector> _traitPreferences;
|
|
|
|
private SpriteView _previewSpriteView => CSpriteView;
|
|
private Button _previewRotateLeftButton => CSpriteRotateLeft;
|
|
private Button _previewRotateRightButton => CSpriteRotateRight;
|
|
private Direction _previewRotation = Direction.North;
|
|
private EntityUid? _previewDummy;
|
|
|
|
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
|
|
private ColorSelectorSliders _rgbSkinColorSelector;
|
|
|
|
private bool _isDirty;
|
|
private bool _needUpdatePreview;
|
|
public int CharacterSlot;
|
|
public HumanoidCharacterProfile? Profile;
|
|
private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
|
|
|
|
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
|
|
|
|
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
|
|
IEntityManager entityManager, IConfigurationManager configurationManager)
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
_prototypeManager = prototypeManager;
|
|
_entMan = entityManager;
|
|
_preferencesManager = preferencesManager;
|
|
_configurationManager = configurationManager;
|
|
_markingManager = IoCManager.Resolve<MarkingManager>();
|
|
|
|
#region Left
|
|
|
|
#region Randomize
|
|
|
|
#endregion Randomize
|
|
|
|
#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"));
|
|
|
|
ShowClothes.OnPressed += ToggleClothes;
|
|
|
|
#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));
|
|
IsDirty = true;
|
|
};
|
|
|
|
_hairPicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsHair();
|
|
IsDirty = true;
|
|
};
|
|
|
|
_facialHairPicker.OnMarkingSelect += newStyle =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
|
|
IsDirty = true;
|
|
};
|
|
|
|
_facialHairPicker.OnColorChanged += newColor =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
|
|
UpdateCMarkingsFacialHair();
|
|
IsDirty = true;
|
|
};
|
|
|
|
_hairPicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
IsDirty = true;
|
|
};
|
|
|
|
_facialHairPicker.OnSlotRemove += _ =>
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
Profile = Profile.WithCharacterAppearance(
|
|
Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
|
|
);
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsFacialHair();
|
|
IsDirty = true;
|
|
};
|
|
|
|
_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();
|
|
IsDirty = true;
|
|
};
|
|
|
|
_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();
|
|
IsDirty = true;
|
|
};
|
|
|
|
#endregion Hair
|
|
|
|
#region Clothing
|
|
|
|
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
|
|
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
|
|
|
|
_clothingButton.OnItemSelected += args =>
|
|
{
|
|
_clothingButton.SelectId(args.Id);
|
|
SetClothing((ClothingPreference) args.Id);
|
|
};
|
|
|
|
#endregion Clothing
|
|
|
|
#region Backpack
|
|
|
|
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
|
|
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
|
|
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
|
|
|
|
_backpackButton.OnItemSelected += args =>
|
|
{
|
|
_backpackButton.SelectId(args.Id);
|
|
SetBackpack((BackpackPreference) args.Id);
|
|
};
|
|
|
|
#endregion Backpack
|
|
|
|
#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;
|
|
IsDirty = true;
|
|
};
|
|
|
|
#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);
|
|
IsDirty = true;
|
|
};
|
|
|
|
_jobPriorities = new List<JobPrioritySelector>();
|
|
_jobCategories = new Dictionary<string, BoxContainer>();
|
|
_requirements = IoCManager.Resolve<JobRequirementsManager>();
|
|
_requirements.Updated += UpdateRoleRequirements;
|
|
UpdateRoleRequirements();
|
|
|
|
#endregion Jobs
|
|
|
|
#region Antags
|
|
|
|
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
|
|
|
|
_antagPreferences = new List<AntagPreferenceSelector>();
|
|
|
|
foreach (var antag in prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
|
|
{
|
|
if (!antag.SetPreference)
|
|
continue;
|
|
|
|
var selector = new AntagPreferenceSelector(antag);
|
|
_antagList.AddChild(selector);
|
|
_antagPreferences.Add(selector);
|
|
if (selector.Disabled)
|
|
{
|
|
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
|
IsDirty = true;
|
|
}
|
|
|
|
selector.PreferenceChanged += preference =>
|
|
{
|
|
Profile = Profile?.WithAntagPreference(antag.ID, preference);
|
|
IsDirty = true;
|
|
};
|
|
}
|
|
|
|
#endregion Antags
|
|
|
|
#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);
|
|
IsDirty = true;
|
|
};
|
|
}
|
|
}
|
|
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();
|
|
_needUpdatePreview = true;
|
|
};
|
|
_previewRotateRightButton.OnPressed += _ =>
|
|
{
|
|
_previewRotation = _previewRotation.TurnCcw();
|
|
_needUpdatePreview = true;
|
|
};
|
|
|
|
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
|
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
|
|
|
if (_previewDummy != null)
|
|
_entMan.DeleteEntity(_previewDummy!.Value);
|
|
|
|
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
|
|
_previewSpriteView.SetEntity(_previewDummy);
|
|
#endregion Dummy
|
|
|
|
#endregion Left
|
|
|
|
if (preferencesManager.ServerDataLoaded)
|
|
{
|
|
LoadServerData();
|
|
}
|
|
|
|
preferencesManager.OnServerDataLoaded += LoadServerData;
|
|
|
|
|
|
IsDirty = false;
|
|
}
|
|
|
|
private void ToggleClothes(BaseButton.ButtonEventArgs obj)
|
|
{
|
|
RebuildSpriteView();
|
|
}
|
|
|
|
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)).ToArray();
|
|
Array.Sort(jobs, JobUIComparer.Instance);
|
|
|
|
foreach (var job in jobs)
|
|
{
|
|
var selector = new JobPrioritySelector(job, _prototypeManager);
|
|
|
|
if (!_requirements.IsAllowed(job, out var reason))
|
|
{
|
|
selector.LockRequirements(reason);
|
|
}
|
|
|
|
category.AddChild(selector);
|
|
_jobPriorities.Add(selector);
|
|
|
|
selector.PriorityChanged += priority =>
|
|
{
|
|
Profile = Profile?.WithJobPriority(job.ID, priority);
|
|
IsDirty = true;
|
|
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
if (Profile is not null)
|
|
{
|
|
UpdateJobPriorities();
|
|
}
|
|
}
|
|
|
|
private void OnFlavorTextChange(string content)
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
Profile = Profile.WithFlavorText(content);
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void OnMarkingChange(MarkingSet markings)
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
|
|
_needUpdatePreview = true;
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void OnMarkingColorChange(List<Marking> markings)
|
|
{
|
|
if (Profile is null)
|
|
return;
|
|
|
|
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
|
|
IsDirty = true;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if (!disposing)
|
|
return;
|
|
|
|
if (_previewDummy != null)
|
|
_entMan.DeleteEntity(_previewDummy.Value);
|
|
|
|
_requirements.Updated -= UpdateRoleRequirements;
|
|
_preferencesManager.OnServerDataLoaded -= LoadServerData;
|
|
}
|
|
|
|
private void RebuildSpriteView()
|
|
{
|
|
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
|
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
|
|
|
if (_previewDummy != null)
|
|
_entMan.DeleteEntity(_previewDummy!.Value);
|
|
|
|
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
|
|
_previewSpriteView.SetEntity(_previewDummy);
|
|
_needUpdatePreview = true;
|
|
}
|
|
|
|
private void LoadServerData()
|
|
{
|
|
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
|
|
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
|
|
|
|
UpdateControls();
|
|
_needUpdatePreview = true;
|
|
}
|
|
|
|
private void SetAge(int newAge)
|
|
{
|
|
Profile = Profile?.WithAge(newAge);
|
|
IsDirty = true;
|
|
}
|
|
|
|
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);
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void SetGender(Gender newGender)
|
|
{
|
|
Profile = Profile?.WithGender(newGender);
|
|
IsDirty = true;
|
|
}
|
|
|
|
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
|
|
RebuildSpriteView(); // they might have different inv so we need a new dummy
|
|
IsDirty = true;
|
|
_needUpdatePreview = true;
|
|
}
|
|
|
|
private void SetName(string newName)
|
|
{
|
|
Profile = Profile?.WithName(newName);
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void SetClothing(ClothingPreference newClothing)
|
|
{
|
|
Profile = Profile?.WithClothingPreference(newClothing);
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void SetBackpack(BackpackPreference newBackpack)
|
|
{
|
|
Profile = Profile?.WithBackpackPreference(newBackpack);
|
|
IsDirty = true;
|
|
}
|
|
|
|
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
|
{
|
|
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
|
IsDirty = true;
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
IsDirty = false;
|
|
|
|
if (Profile != null)
|
|
{
|
|
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
|
|
OnProfileChanged?.Invoke(Profile, CharacterSlot);
|
|
_needUpdatePreview = true;
|
|
}
|
|
}
|
|
|
|
private bool IsDirty
|
|
{
|
|
get => _isDirty;
|
|
set
|
|
{
|
|
_isDirty = value;
|
|
_needUpdatePreview = true;
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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 UpdateClothingControls()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_clothingButton.SelectId((int) Profile.Clothing);
|
|
}
|
|
|
|
private void UpdateBackpackControls()
|
|
{
|
|
if (Profile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_backpackButton.SelectId((int) Profile.Backpack);
|
|
}
|
|
|
|
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;
|
|
|
|
var humanoid = _entMan.System<HumanoidAppearanceSystem>();
|
|
humanoid.LoadProfile(_previewDummy!.Value, Profile);
|
|
|
|
if (ShowClothes.Pressed)
|
|
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
|
|
|
|
_previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
|
|
}
|
|
|
|
public void UpdateControls()
|
|
{
|
|
if (Profile is null) return;
|
|
UpdateNameEdit();
|
|
UpdateFlavorTextEdit();
|
|
UpdateSexControls();
|
|
UpdateGenderControls();
|
|
UpdateSkinColor();
|
|
UpdateSpecies();
|
|
UpdateClothingControls();
|
|
UpdateBackpackControls();
|
|
UpdateSpawnPriorityControls();
|
|
UpdateAgeEdit();
|
|
UpdateEyePickers();
|
|
UpdateSaveButton();
|
|
UpdateJobPriorities();
|
|
UpdateAntagPreferences();
|
|
UpdateTraitPreferences();
|
|
UpdateMarkings();
|
|
RebuildSpriteView();
|
|
UpdateHairPickers();
|
|
UpdateCMarkingsHair();
|
|
UpdateCMarkingsFacialHair();
|
|
|
|
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
|
|
}
|
|
|
|
protected override void FrameUpdate(FrameEventArgs args)
|
|
{
|
|
base.FrameUpdate(args);
|
|
|
|
if (_needUpdatePreview)
|
|
{
|
|
UpdatePreview();
|
|
_needUpdatePreview = false;
|
|
}
|
|
}
|
|
|
|
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 abstract class RequirementsSelector<T> : Control
|
|
{
|
|
public T Proto { get; }
|
|
public bool Disabled => _lockStripe.Visible;
|
|
|
|
protected readonly RadioOptions<int> Options;
|
|
private StripeBack _lockStripe;
|
|
private Label _requirementsLabel;
|
|
|
|
protected RequirementsSelector(T proto)
|
|
{
|
|
Proto = proto;
|
|
|
|
Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
|
|
{
|
|
FirstButtonStyle = StyleBase.ButtonOpenRight,
|
|
ButtonStyle = StyleBase.ButtonOpenBoth,
|
|
LastButtonStyle = StyleBase.ButtonOpenLeft
|
|
};
|
|
//Override default radio option button width
|
|
Options.GenerateItem = GenerateButton;
|
|
|
|
Options.OnItemSelected += args => Options.Select(args.Id);
|
|
|
|
_requirementsLabel = new Label()
|
|
{
|
|
Text = Loc.GetString("role-timer-locked"),
|
|
Visible = true,
|
|
HorizontalAlignment = HAlignment.Center,
|
|
StyleClasses = {StyleBase.StyleClassLabelSubText},
|
|
};
|
|
|
|
_lockStripe = new StripeBack()
|
|
{
|
|
Visible = false,
|
|
HorizontalExpand = true,
|
|
MouseFilter = MouseFilterMode.Stop,
|
|
Children =
|
|
{
|
|
_requirementsLabel
|
|
}
|
|
};
|
|
|
|
// Setup must be called after
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actually adds the controls, must be called in the inheriting class' constructor.
|
|
/// </summary>
|
|
protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
|
|
{
|
|
foreach (var (text, value) in items)
|
|
{
|
|
Options.AddItem(Loc.GetString(text), value);
|
|
}
|
|
|
|
var titleLabel = new Label()
|
|
{
|
|
Margin = new Thickness(5f, 0, 5f, 0),
|
|
Text = title,
|
|
MinSize = new Vector2(titleSize, 0),
|
|
MouseFilter = MouseFilterMode.Stop,
|
|
ToolTip = description
|
|
};
|
|
|
|
var container = new BoxContainer
|
|
{
|
|
Orientation = LayoutOrientation.Horizontal,
|
|
};
|
|
|
|
if (icon != null)
|
|
container.AddChild(icon);
|
|
container.AddChild(titleLabel);
|
|
container.AddChild(Options);
|
|
container.AddChild(_lockStripe);
|
|
|
|
AddChild(container);
|
|
}
|
|
|
|
public void LockRequirements(FormattedMessage requirements)
|
|
{
|
|
var tooltip = new Tooltip();
|
|
tooltip.SetMessage(requirements);
|
|
_lockStripe.TooltipSupplier = _ => tooltip;
|
|
_lockStripe.Visible = true;
|
|
Options.Visible = false;
|
|
}
|
|
|
|
// TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
|
|
public void UnlockRequirements()
|
|
{
|
|
_lockStripe.Visible = false;
|
|
Options.Visible = true;
|
|
}
|
|
|
|
private Button GenerateButton(string text, int value)
|
|
{
|
|
return new Button
|
|
{
|
|
Text = text,
|
|
MinWidth = 90
|
|
};
|
|
}
|
|
}
|
|
|
|
private sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
|
|
{
|
|
public JobPriority Priority
|
|
{
|
|
get => (JobPriority) Options.SelectedValue;
|
|
set => Options.SelectByValue((int) value);
|
|
}
|
|
|
|
public event Action<JobPriority>? PriorityChanged;
|
|
|
|
public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
|
|
: base(proto)
|
|
{
|
|
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
|
|
|
|
var items = new[]
|
|
{
|
|
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
|
|
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
|
|
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
|
|
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
|
|
};
|
|
|
|
var icon = new TextureRect
|
|
{
|
|
TextureScale = new Vector2(2, 2),
|
|
VerticalAlignment = VAlignment.Center
|
|
};
|
|
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
|
|
icon.Texture = jobIcon.Icon.Frame0();
|
|
|
|
Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
|
|
}
|
|
}
|
|
|
|
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 AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
|
|
{
|
|
// 0 is yes and 1 is no
|
|
public bool Preference
|
|
{
|
|
get => Options.SelectedValue == 0;
|
|
set => Options.Select((value && !Disabled) ? 0 : 1);
|
|
}
|
|
|
|
public event Action<bool>? PreferenceChanged;
|
|
|
|
public AntagPreferenceSelector(AntagPrototype proto)
|
|
: base(proto)
|
|
{
|
|
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
|
|
|
|
var items = new[]
|
|
{
|
|
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
|
("humanoid-profile-editor-antag-preference-no-button", 1)
|
|
};
|
|
var title = Loc.GetString(proto.Name);
|
|
var description = Loc.GetString(proto.Objective);
|
|
Setup(items, title, 250, description);
|
|
|
|
// immediately lock requirements if they arent met.
|
|
// another function checks Disabled after creating the selector so this has to be done now
|
|
var requirements = IoCManager.Resolve<JobRequirementsManager>();
|
|
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
|
|
{
|
|
LockRequirements(reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|