| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- using System.Linq;
- using Content.Shared.Humanoid;
- using Content.Shared.Humanoid.Markings;
- using Content.Shared.Humanoid.Prototypes;
- using Robust.Client.AutoGenerated;
- using Robust.Client.UserInterface;
- using Robust.Client.UserInterface.Controls;
- using Robust.Client.UserInterface.XAML;
- using Robust.Client.Utility;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Utility;
- using static Robust.Client.UserInterface.Controls.BoxContainer;
- namespace Content.Client.Humanoid;
- [GenerateTypedNameReferences]
- public sealed partial class MarkingPicker : Control
- {
- [Dependency] private readonly MarkingManager _markingManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- public Action<MarkingSet>? OnMarkingAdded;
- public Action<MarkingSet>? OnMarkingRemoved;
- public Action<MarkingSet>? OnMarkingColorChange;
- public Action<MarkingSet>? OnMarkingRankChange;
- private List<Color> _currentMarkingColors = new();
- private ItemList.Item? _selectedMarking;
- private ItemList.Item? _selectedUnusedMarking;
- private MarkingCategories _selectedMarkingCategory = MarkingCategories.Chest;
- private MarkingSet _currentMarkings = new();
- private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList();
- private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies;
- private Sex _currentSex = Sex.Unsexed;
- public Color CurrentSkinColor = Color.White;
- public Color CurrentEyeColor = Color.Black;
- public Marking? HairMarking;
- public Marking? FacialHairMarking;
- private readonly HashSet<MarkingCategories> _ignoreCategories = new();
- public string IgnoreCategories
- {
- get => string.Join(',', _ignoreCategories);
- set
- {
- _ignoreCategories.Clear();
- var split = value.Split(',');
- foreach (var category in split)
- {
- if (!Enum.TryParse(category, out MarkingCategories categoryParse))
- {
- continue;
- }
- _ignoreCategories.Add(categoryParse);
- }
- SetupCategoryButtons();
- }
- }
- public bool Forced { get; set; }
- private bool _ignoreSpecies;
- public bool IgnoreSpecies
- {
- get => _ignoreSpecies;
- set
- {
- _ignoreSpecies = value;
- Populate(CMarkingSearch.Text);
- }
- }
- public void SetData(List<Marking> newMarkings, string species, Sex sex, Color skinColor, Color eyeColor)
- {
- var pointsProto = _prototypeManager
- .Index<SpeciesPrototype>(species).MarkingPoints;
- _currentMarkings = new(newMarkings, pointsProto, _markingManager);
- if (!IgnoreSpecies)
- {
- _currentMarkings.EnsureSpecies(species, skinColor, _markingManager); // should be validated server-side but it can't hurt
- }
- _currentSpecies = species;
- _currentSex = sex;
- CurrentSkinColor = skinColor;
- CurrentEyeColor = eyeColor;
- Populate(CMarkingSearch.Text);
- PopulateUsed();
- }
- public void SetData(MarkingSet set, string species, Sex sex, Color skinColor, Color eyeColor)
- {
- _currentMarkings = set;
- if (!IgnoreSpecies)
- {
- _currentMarkings.EnsureSpecies(species, skinColor, _markingManager); // should be validated server-side but it can't hurt
- }
- _currentSpecies = species;
- _currentSex = sex;
- CurrentSkinColor = skinColor;
- CurrentEyeColor = eyeColor;
- Populate(CMarkingSearch.Text);
- PopulateUsed();
- }
- public void SetSkinColor(Color color) => CurrentSkinColor = color;
- public void SetEyeColor(Color color) => CurrentEyeColor = color;
- public MarkingPicker()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
- CMarkingCategoryButton.OnItemSelected += OnCategoryChange;
- CMarkingsUnused.OnItemSelected += item =>
- _selectedUnusedMarking = CMarkingsUnused[item.ItemIndex];
- CMarkingAdd.OnPressed += _ =>
- MarkingAdd();
- CMarkingsUsed.OnItemSelected += OnUsedMarkingSelected;
- CMarkingRemove.OnPressed += _ =>
- MarkingRemove();
- CMarkingRankUp.OnPressed += _ => SwapMarkingUp();
- CMarkingRankDown.OnPressed += _ => SwapMarkingDown();
- CMarkingSearch.OnTextChanged += args => Populate(args.Text);
- }
- private void SetupCategoryButtons()
- {
- CMarkingCategoryButton.Clear();
- var validCategories = new List<MarkingCategories>();
- for (var i = 0; i < _markingCategories.Count; i++)
- {
- var category = _markingCategories[i];
- var markings = GetMarkings(category);
- if (_ignoreCategories.Contains(category) ||
- markings.Count == 0)
- {
- continue;
- }
- validCategories.Add(category);
- CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{category.ToString()}"), i);
- }
- if (validCategories.Contains(_selectedMarkingCategory))
- {
- CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(_selectedMarkingCategory));
- }
- else if (validCategories.Count > 0)
- {
- _selectedMarkingCategory = validCategories[0];
- }
- else
- {
- _selectedMarkingCategory = MarkingCategories.Chest;
- }
- }
- private string GetMarkingName(MarkingPrototype marking) => Loc.GetString($"marking-{marking.ID}");
- private List<string> GetMarkingStateNames(MarkingPrototype marking)
- {
- List<string> result = new();
- foreach (var markingState in marking.Sprites)
- {
- switch (markingState)
- {
- case SpriteSpecifier.Rsi rsi:
- result.Add(Loc.GetString($"marking-{marking.ID}-{rsi.RsiState}"));
- break;
- case SpriteSpecifier.Texture texture:
- result.Add(Loc.GetString($"marking-{marking.ID}-{texture.TexturePath.Filename}"));
- break;
- }
- }
- return result;
- }
- private IReadOnlyDictionary<string, MarkingPrototype> GetMarkings(MarkingCategories category)
- {
- return IgnoreSpecies
- ? _markingManager.MarkingsByCategoryAndSex(category, _currentSex)
- : _markingManager.MarkingsByCategoryAndSpeciesAndSex(category, _currentSpecies, _currentSex);
- }
- public void Populate(string filter)
- {
- SetupCategoryButtons();
- CMarkingsUnused.Clear();
- _selectedUnusedMarking = null;
- var sortedMarkings = GetMarkings(_selectedMarkingCategory).Values.Where(m =>
- m.ID.ToLower().Contains(filter.ToLower()) ||
- GetMarkingName(m).ToLower().Contains(filter.ToLower())
- ).OrderBy(p => Loc.GetString(GetMarkingName(p)));
- foreach (var marking in sortedMarkings)
- {
- if (_currentMarkings.TryGetMarking(_selectedMarkingCategory, marking.ID, out _))
- {
- continue;
- }
- var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
- item.Metadata = marking;
- }
- CMarkingPoints.Visible = _currentMarkings.PointsLeft(_selectedMarkingCategory) != -1;
- }
- // Populate the used marking list. Returns a list of markings that weren't
- // valid to add to the marking list.
- public void PopulateUsed()
- {
- CMarkingsUsed.Clear();
- CMarkingColors.Visible = false;
- _selectedMarking = null;
- if (!IgnoreSpecies)
- {
- _currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager);
- }
- // walk backwards through the list for visual purposes
- foreach (var marking in _currentMarkings.GetReverseEnumerator(_selectedMarkingCategory))
- {
- if (!_markingManager.TryGetMarking(marking, out var newMarking))
- {
- continue;
- }
- var text = Loc.GetString(marking.Forced ? "marking-used-forced" : "marking-used", ("marking-name", $"{GetMarkingName(newMarking)}"),
- ("marking-category", Loc.GetString($"markings-category-{newMarking.MarkingCategory}")));
- var _item = new ItemList.Item(CMarkingsUsed)
- {
- Text = text,
- Icon = newMarking.Sprites[0].Frame0(),
- Selectable = true,
- Metadata = newMarking,
- IconModulate = marking.MarkingColors[0]
- };
- CMarkingsUsed.Add(_item);
- }
- // since all the points have been processed, update the points visually
- UpdatePoints();
- }
- private void SwapMarkingUp()
- {
- if (_selectedMarking == null)
- {
- return;
- }
- var i = CMarkingsUsed.IndexOf(_selectedMarking);
- if (ShiftMarkingRank(i, -1))
- {
- OnMarkingRankChange?.Invoke(_currentMarkings);
- }
- }
- private void SwapMarkingDown()
- {
- if (_selectedMarking == null)
- {
- return;
- }
- var i = CMarkingsUsed.IndexOf(_selectedMarking);
- if (ShiftMarkingRank(i, 1))
- {
- OnMarkingRankChange?.Invoke(_currentMarkings);
- }
- }
- private bool ShiftMarkingRank(int src, int places)
- {
- if (src + places >= CMarkingsUsed.Count || src + places < 0)
- {
- return false;
- }
- var visualDest = src + places; // what it would visually look like
- var visualTemp = CMarkingsUsed[visualDest];
- CMarkingsUsed[visualDest] = CMarkingsUsed[src];
- CMarkingsUsed[src] = visualTemp;
- switch (places)
- {
- // i.e., we're going down in rank
- case < 0:
- _currentMarkings.ShiftRankDownFromEnd(_selectedMarkingCategory, src);
- break;
- // i.e., we're going up in rank
- case > 0:
- _currentMarkings.ShiftRankUpFromEnd(_selectedMarkingCategory, src);
- break;
- // do nothing?
- // ReSharper disable once RedundantEmptySwitchSection
- default:
- break;
- }
- return true;
- }
- // repopulate in case markings are restricted,
- // and also filter out any markings that are now invalid
- // attempt to preserve any existing markings as well:
- // it would be frustrating to otherwise have all markings
- // cleared, imo
- public void SetSpecies(string species)
- {
- _currentSpecies = species;
- var markingList = _currentMarkings.GetForwardEnumerator().ToList();
- var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(species);
- _currentMarkings = new(markingList, speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
- _currentMarkings.EnsureSpecies(species, null, _markingManager);
- _currentMarkings.EnsureSexes(_currentSex, _markingManager);
- Populate(CMarkingSearch.Text);
- PopulateUsed();
- }
- public void SetSex(Sex sex)
- {
- _currentSex = sex;
- var markingList = _currentMarkings.GetForwardEnumerator().ToList();
- var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(_currentSpecies);
- _currentMarkings = new(markingList, speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
- _currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager);
- _currentMarkings.EnsureSexes(_currentSex, _markingManager);
- Populate(CMarkingSearch.Text);
- PopulateUsed();
- }
- private void UpdatePoints()
- {
- var count = _currentMarkings.PointsLeft(_selectedMarkingCategory);
- if (count > -1)
- {
- CMarkingPoints.Text = Loc.GetString("marking-points-remaining", ("points", count));
- }
- }
- private void OnCategoryChange(OptionButton.ItemSelectedEventArgs category)
- {
- CMarkingCategoryButton.SelectId(category.Id);
- _selectedMarkingCategory = _markingCategories[category.Id];
- Populate(CMarkingSearch.Text);
- PopulateUsed();
- UpdatePoints();
- }
- // TODO: This should be using ColorSelectorSliders once that's merged, so
- private void OnUsedMarkingSelected(ItemList.ItemListSelectedEventArgs item)
- {
- _selectedMarking = CMarkingsUsed[item.ItemIndex];
- var prototype = (MarkingPrototype) _selectedMarking.Metadata!;
- if (prototype.ForcedColoring)
- {
- CMarkingColors.Visible = false;
- return;
- }
- var stateNames = GetMarkingStateNames(prototype);
- _currentMarkingColors.Clear();
- CMarkingColors.DisposeAllChildren();
- List<ColorSelectorSliders> colorSliders = new();
- for (int i = 0; i < prototype.Sprites.Count; i++)
- {
- var colorContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- };
- CMarkingColors.AddChild(colorContainer);
- ColorSelectorSliders colorSelector = new ColorSelectorSliders();
- colorSliders.Add(colorSelector);
- colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });
- colorContainer.AddChild(colorSelector);
- var listing = _currentMarkings.Markings[_selectedMarkingCategory];
- var color = listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i];
- var currentColor = new Color(
- color.RByte,
- color.GByte,
- color.BByte
- );
- colorSelector.Color = currentColor;
- _currentMarkingColors.Add(currentColor);
- var colorIndex = _currentMarkingColors.Count - 1;
- Action<Color> colorChanged = _ =>
- {
- _currentMarkingColors[colorIndex] = colorSelector.Color;
- ColorChanged(colorIndex);
- };
- colorSelector.OnColorChanged += colorChanged;
- }
- CMarkingColors.Visible = true;
- }
- private void ColorChanged(int colorIndex)
- {
- if (_selectedMarking is null) return;
- var markingPrototype = (MarkingPrototype) _selectedMarking.Metadata!;
- int markingIndex = _currentMarkings.FindIndexOf(_selectedMarkingCategory, markingPrototype.ID);
- if (markingIndex < 0) return;
- _selectedMarking.IconModulate = _currentMarkingColors[colorIndex];
- var marking = new Marking(_currentMarkings.Markings[_selectedMarkingCategory][markingIndex]);
- marking.SetColor(colorIndex, _currentMarkingColors[colorIndex]);
- _currentMarkings.Replace(_selectedMarkingCategory, markingIndex, marking);
- OnMarkingColorChange?.Invoke(_currentMarkings);
- }
- private void MarkingAdd()
- {
- if (_selectedUnusedMarking is null) return;
- if (_currentMarkings.PointsLeft(_selectedMarkingCategory) == 0 && !Forced)
- {
- return;
- }
- var marking = (MarkingPrototype) _selectedUnusedMarking.Metadata!;
- var markingObject = marking.AsMarking();
- // We need add hair markings in cloned set manually because _currentMarkings doesn't have it
- var markingSet = new MarkingSet(_currentMarkings);
- if (HairMarking != null)
- {
- markingSet.AddBack(MarkingCategories.Hair, HairMarking);
- }
- if (FacialHairMarking != null)
- {
- markingSet.AddBack(MarkingCategories.FacialHair, FacialHairMarking);
- }
- if (!_markingManager.MustMatchSkin(_currentSpecies, marking.BodyPart, out var _, _prototypeManager))
- {
- // Do default coloring
- var colors = MarkingColoring.GetMarkingLayerColors(
- marking,
- CurrentSkinColor,
- CurrentEyeColor,
- markingSet
- );
- for (var i = 0; i < colors.Count; i++)
- {
- markingObject.SetColor(i, colors[i]);
- }
- }
- else
- {
- // Color everything in skin color
- for (var i = 0; i < marking.Sprites.Count; i++)
- {
- markingObject.SetColor(i, CurrentSkinColor);
- }
- }
- markingObject.Forced = Forced;
- _currentMarkings.AddBack(_selectedMarkingCategory, markingObject);
- UpdatePoints();
- CMarkingsUnused.Remove(_selectedUnusedMarking);
- var item = new ItemList.Item(CMarkingsUsed)
- {
- Text = Loc.GetString("marking-used", ("marking-name", $"{GetMarkingName(marking)}"), ("marking-category", Loc.GetString($"markings-category-{marking.MarkingCategory}"))),
- Icon = marking.Sprites[0].Frame0(),
- Selectable = true,
- Metadata = marking,
- };
- CMarkingsUsed.Insert(0, item);
- _selectedUnusedMarking = null;
- OnMarkingAdded?.Invoke(_currentMarkings);
- }
- private void MarkingRemove()
- {
- if (_selectedMarking is null) return;
- var marking = (MarkingPrototype) _selectedMarking.Metadata!;
- _currentMarkings.Remove(_selectedMarkingCategory, marking.ID);
- UpdatePoints();
- CMarkingsUsed.Remove(_selectedMarking);
- if (marking.MarkingCategory == _selectedMarkingCategory)
- {
- var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
- item.Metadata = marking;
- }
- _selectedMarking = null;
- CMarkingColors.Visible = false;
- OnMarkingRemoved?.Invoke(_currentMarkings);
- }
- }
|