]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Make department / job list sorting consistent. (#25486)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Fri, 23 Feb 2024 04:04:44 +0000 (05:04 +0100)
committerGitHub <noreply@github.com>
Fri, 23 Feb 2024 04:04:44 +0000 (15:04 +1100)
* 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!

Content.Client/CrewManifest/UI/CrewManifestListing.cs
Content.Client/CrewManifest/UI/CrewManifestSection.cs
Content.Client/LateJoin/LateJoinGui.cs
Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
Content.Server/CrewManifest/CrewManifestSystem.cs
Content.Server/StationRecords/Systems/StationRecordsSystem.cs
Content.Shared/CCVar/CCVars.cs
Content.Shared/CrewManifest/SharedCrewManifestSystem.cs
Content.Shared/Roles/DepartmentPrototype.cs
Content.Shared/Roles/JobPrototype.cs

index 614add536e02e0da6219ae22f2f9d08c67307c3b..03d8b7168f1668701d2e0afd9fbc1b8e14609e0d 100644 (file)
@@ -1,18 +1,14 @@
-using Content.Shared.CCVar;
-using Content.Shared.CrewManifest;
+using Content.Shared.CrewManifest;
 using Content.Shared.Roles;
 using Robust.Client.GameObjects;
 using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Configuration;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
-using System.Linq;
 
 namespace Content.Client.CrewManifest.UI;
 
 public sealed class CrewManifestListing : BoxContainer
 {
-    [Dependency] private readonly IConfigurationManager _configManager = default!;
     [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     private readonly SpriteSystem _spriteSystem;
@@ -25,7 +21,7 @@ public sealed class CrewManifestListing : BoxContainer
 
     public void AddCrewManifestEntries(CrewManifestEntries entries)
     {
-        var entryDict = new Dictionary<string, List<CrewManifestEntry>>();
+        var entryDict = new Dictionary<DepartmentPrototype, List<CrewManifestEntry>>();
 
         foreach (var entry in entries.Entries)
         {
@@ -34,37 +30,19 @@ public sealed class CrewManifestListing : BoxContainer
                 // this is a little expensive, and could be better
                 if (department.Roles.Contains(entry.JobPrototype))
                 {
-                    entryDict.GetOrNew(department.ID).Add(entry);
+                    entryDict.GetOrNew(department).Add(entry);
                 }
             }
         }
 
-        var entryList = new List<(string section, List<CrewManifestEntry> entries)>();
+        var entryList = new List<(DepartmentPrototype section, List<CrewManifestEntry> entries)>();
 
         foreach (var (section, listing) in entryDict)
         {
             entryList.Add((section, listing));
         }
 
-        var sortOrder = _configManager.GetCVar(CCVars.CrewManifestOrdering).Split(",").ToList();
-
-        entryList.Sort((a, b) =>
-        {
-            var ai = sortOrder.IndexOf(a.section);
-            var bi = sortOrder.IndexOf(b.section);
-
-            // this is up here so -1 == -1 occurs first
-            if (ai == bi)
-                return 0;
-
-            if (ai == -1)
-                return -1;
-
-            if (bi == -1)
-                return 1;
-
-            return ai.CompareTo(bi);
-        });
+        entryList.Sort((a, b) => DepartmentUIComparer.Instance.Compare(a.section, b.section));
 
         foreach (var item in entryList)
         {
index 9b51b5d4241a3b6da3f80e45eb1f346c01458427..4b1b02cb75594d2a821881b9e46cee183374d315 100644 (file)
@@ -4,24 +4,25 @@ using Robust.Client.GameObjects;
 using Robust.Client.UserInterface.Controls;
 using Robust.Shared.Prototypes;
 using System.Numerics;
+using Content.Shared.Roles;
 
 namespace Content.Client.CrewManifest.UI;
 
 public sealed class CrewManifestSection : BoxContainer
 {
-    public CrewManifestSection(IPrototypeManager prototypeManager, SpriteSystem spriteSystem, string sectionTitle,
+    public CrewManifestSection(
+        IPrototypeManager prototypeManager,
+        SpriteSystem spriteSystem,
+        DepartmentPrototype section,
         List<CrewManifestEntry> entries)
     {
         Orientation = LayoutOrientation.Vertical;
         HorizontalExpand = true;
 
-        if (Loc.TryGetString($"department-{sectionTitle}", out var localizedDepart))
-            sectionTitle = localizedDepart;
-
         AddChild(new Label()
         {
             StyleClasses = { "LabelBig" },
-            Text = Loc.GetString(sectionTitle)
+            Text = Loc.GetString($"department-{section.ID}")
         });
 
         var gridContainer = new GridContainer()
@@ -55,8 +56,9 @@ public sealed class CrewManifestSection : BoxContainer
                 var icon = new TextureRect()
                 {
                     TextureScale = new Vector2(2, 2),
-                    Stretch = TextureRect.StretchMode.KeepCentered,
+                    VerticalAlignment = VAlignment.Center,
                     Texture = spriteSystem.Frame0(jobIcon.Icon),
+                    Margin = new Thickness(0, 0, 4, 0)
                 };
 
                 titleContainer.AddChild(icon);
index 3e7ca574763ba2efdd9d3877cba5d9acdf933364..b99d30015ef8e10ae8a47294e1177b886a904b81 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using System.Numerics;
 using Content.Client.CrewManifest;
 using Content.Client.GameTicking.Managers;
@@ -159,8 +160,10 @@ namespace Content.Client.LateJoin
                 };
 
                 var firstCategory = true;
+                var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
+                Array.Sort(departments, DepartmentUIComparer.Instance);
 
-                foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
+                foreach (var department in departments)
                 {
                     var departmentName = Loc.GetString($"department-{department.ID}");
                     _jobCategories[id] = new Dictionary<string, BoxContainer>();
@@ -176,7 +179,7 @@ namespace Content.Client.LateJoin
                         jobsAvailable.Add(_prototypeManager.Index<JobPrototype>(jobId));
                     }
 
-                    jobsAvailable.Sort((x, y) => -string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCultureIgnoreCase));
+                    jobsAvailable.Sort(JobUIComparer.Instance);
 
                     // Do not display departments with no jobs available.
                     if (jobsAvailable.Count == 0)
@@ -231,7 +234,7 @@ namespace Content.Client.LateJoin
                         var icon = new TextureRect
                         {
                             TextureScale = new Vector2(2, 2),
-                            Stretch = TextureRect.StretchMode.KeepCentered
+                            VerticalAlignment = VAlignment.Center
                         };
 
                         var jobIcon = _prototypeManager.Index<StatusIconPrototype>(prototype.Icon);
index d8c87899db419b99e6fbfe11ceae4d2fe5ad1e30..645243b0a3a15d69bb0ecf2e6f64cede037b0270 100644 (file)
@@ -265,7 +265,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
                 var jobIcon = new TextureRect()
                 {
                     TextureScale = new Vector2(2f, 2f),
-                    Stretch = TextureRect.StretchMode.KeepCentered,
+                    VerticalAlignment = VAlignment.Center,
                     Texture = _spriteSystem.Frame0(proto.Icon),
                     Margin = new Thickness(5, 0, 5, 0),
                 };
index b88590dd4623b7f9aa9ae2a51670bc652d3a3175..9270533642478b52364be35adb1a9e2f003dd2b0 100644 (file)
@@ -539,10 +539,8 @@ namespace Content.Client.Preferences.UI
             _jobCategories.Clear();
             var firstCategory = true;
 
-            var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>()
-                .OrderByDescending(department => department.Weight)
-                .ThenBy(department => Loc.GetString($"department-{department.ID}"))
-                .ToList();
+            var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
+            Array.Sort(departments, DepartmentUIComparer.Instance);
 
             foreach (var department in departments)
             {
@@ -588,11 +586,8 @@ namespace Content.Client.Preferences.UI
                     _jobList.AddChild(category);
                 }
 
-                var jobs = department.Roles.Select(jobId => _prototypeManager.Index<JobPrototype>(jobId))
-                    .Where(job => job.SetPreference)
-                    .OrderByDescending(job => job.Weight)
-                    .ThenBy(job => job.LocalizedName)
-                    .ToList();
+                var jobs = department.Roles.Select(jobId => _prototypeManager.Index<JobPrototype>(jobId)).ToArray();
+                Array.Sort(jobs, JobUIComparer.Instance);
 
                 foreach (var job in jobs)
                 {
@@ -1315,7 +1310,7 @@ namespace Content.Client.Preferences.UI
                 var icon = new TextureRect
                 {
                     TextureScale = new Vector2(2, 2),
-                    Stretch = TextureRect.StretchMode.KeepCentered
+                    VerticalAlignment = VAlignment.Center
                 };
                 var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
                 icon.Texture = jobIcon.Icon.Frame0();
index 12b6984b5aeab19fe243c7e21a66662f76d752d8..8b4cbac5c156e7b7a0fd272a23604c7e05448ca6 100644 (file)
@@ -9,10 +9,12 @@ using Content.Shared.Administration;
 using Content.Shared.CCVar;
 using Content.Shared.CrewManifest;
 using Content.Shared.GameTicking;
+using Content.Shared.Roles;
 using Content.Shared.StationRecords;
 using Robust.Shared.Configuration;
 using Robust.Shared.Console;
 using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
 namespace Content.Server.CrewManifest;
@@ -23,6 +25,7 @@ public sealed class CrewManifestSystem : EntitySystem
     [Dependency] private readonly StationRecordsSystem _recordsSystem = default!;
     [Dependency] private readonly EuiManager _euiManager = default!;
     [Dependency] private readonly IConfigurationManager _configManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
 
     /// <summary>
     ///     Cached crew manifest entries. The alternative is to outright
@@ -223,15 +226,26 @@ public sealed class CrewManifestSystem : EntitySystem
 
         var entries = new CrewManifestEntries();
 
+        var entriesSort = new List<(JobPrototype? job, CrewManifestEntry entry)>();
         foreach (var recordObject in iter)
         {
             var record = recordObject.Item2;
             var entry = new CrewManifestEntry(record.Name, record.JobTitle, record.JobIcon, record.JobPrototype);
 
-            entries.Entries.Add(entry);
+            _prototypeManager.TryIndex(record.JobPrototype, out JobPrototype? job);
+            entriesSort.Add((job, entry));
         }
 
-        entries.Entries = entries.Entries.OrderBy(e => e.JobTitle).ThenBy(e => e.Name).ToList();
+        entriesSort.Sort((a, b) =>
+        {
+            var cmp = JobUIComparer.Instance.Compare(a.job, b.job);
+            if (cmp != 0)
+                return cmp;
+
+            return string.Compare(a.entry.Name, b.entry.Name, StringComparison.CurrentCultureIgnoreCase);
+        });
+
+        entries.Entries = entriesSort.Select(x => x.entry).ToArray();
         _cachedEntries[station] = entries;
     }
 }
index 09a00e5967cd4c51ad939cb8c66ecffd0d5e0d77..67f50d7a4e126d24058f93a75874806c90cbe043 100644 (file)
@@ -129,7 +129,7 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
             JobPrototype = jobId,
             Species = species,
             Gender = gender,
-            DisplayPriority = jobPrototype.Weight,
+            DisplayPriority = jobPrototype.RealDisplayWeight,
             Fingerprint = mobFingerprint,
             DNA = dna
         };
index bc90d7942c2c8a83c4a7e629637617e83028dabd..552db94f4daf7e0934b352869a22acbce3549316 100644 (file)
@@ -1479,15 +1479,6 @@ namespace Content.Shared.CCVar
         public static readonly CVarDef<bool> CrewManifestUnsecure =
             CVarDef.Create("crewmanifest.unsecure", true, CVar.REPLICATED);
 
-        /// <summary>
-        ///     Dictates the order the crew manifest will appear in, in terms of its sections.
-        ///     Sections not in this list will appear at the end of the list, in no
-        ///     specific order.
-        /// </summary>
-        public static readonly CVarDef<string> CrewManifestOrdering =
-            CVarDef.Create("crewmanifest.ordering", "Command,Security,Science,Medical,Engineering,Cargo,Civilian,Unknown",
-                CVar.REPLICATED);
-
         /*
          * Biomass
          */
index 7e4c824e205ebd19f80d1fd2592a3696c65b06be..a9279cc7f1fb5ab21fd9b1c04365c07627d45fcd 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Eui;
+using NetSerializer;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.CrewManifest;
@@ -39,7 +40,7 @@ public sealed class CrewManifestEntries
     ///     Entries in the crew manifest. Goes by department ID.
     /// </summary>
     // public Dictionary<string, List<CrewManifestEntry>> Entries = new();
-    public List<CrewManifestEntry> Entries = new();
+    public CrewManifestEntry[] Entries = Array.Empty<CrewManifestEntry>();
 }
 
 [Serializable, NetSerializable]
index f79b03f4a606a90138029ab2ceee479af2868dfb..024eca37fa2d9528da702ddc5254e46fdb88e582 100644 (file)
@@ -37,3 +37,27 @@ public sealed partial class DepartmentPrototype : IPrototype
     [DataField("weight")]
     public int Weight { get; private set; } = 0;
 }
+
+/// <summary>
+/// Sorts <see cref="DepartmentPrototype"/> appropriately for display in the UI,
+/// respecting their <see cref="DepartmentPrototype.Weight"/>.
+/// </summary>
+public sealed class DepartmentUIComparer : IComparer<DepartmentPrototype>
+{
+    public static readonly DepartmentUIComparer Instance = new();
+
+    public int Compare(DepartmentPrototype? x, DepartmentPrototype? y)
+    {
+        if (ReferenceEquals(x, y))
+            return 0;
+        if (ReferenceEquals(null, y))
+            return 1;
+        if (ReferenceEquals(null, x))
+            return -1;
+
+        var cmp = -x.Weight.CompareTo(y.Weight);
+        if (cmp != 0)
+            return cmp;
+        return string.Compare(x.ID, y.ID, StringComparison.Ordinal);
+    }
+}
index acb88e16f0a336794f0a4ede5ad32bc2839af41b..0064fcdf17ee2a99f0b8a3b1306732d15ca32729 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Access;
 using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Roles;
 using Content.Shared.StatusIcon;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -70,6 +71,16 @@ namespace Content.Shared.Roles
         [DataField("weight")]
         public int Weight { get; private set; }
 
+        /// <summary>
+        /// How to sort this job relative to other jobs in the UI.
+        /// Jobs with a higher value with sort before jobs with a lower value.
+        /// If not set, <see cref="Weight"/> is used as a fallback.
+        /// </summary>
+        [DataField]
+        public int? DisplayWeight { get; private set; }
+
+        public int RealDisplayWeight => DisplayWeight ?? Weight;
+
         /// <summary>
         ///     A numerical score for how much easier this job is for antagonists.
         ///     For traitors, reduces starting TC by this amount. Other gamemodes can use it for whatever they find fitting.
@@ -106,4 +117,28 @@ namespace Content.Shared.Roles
         [DataField("extendedAccessGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessGroupPrototype>))]
         public IReadOnlyCollection<string> ExtendedAccessGroups { get; private set; } = Array.Empty<string>();
     }
+
+    /// <summary>
+    /// Sorts <see cref="JobPrototype"/>s appropriately for display in the UI,
+    /// respecting their <see cref="JobPrototype.Weight"/>.
+    /// </summary>
+    public sealed class JobUIComparer : IComparer<JobPrototype>
+    {
+        public static readonly JobUIComparer Instance = new();
+
+        public int Compare(JobPrototype? x, JobPrototype? y)
+        {
+            if (ReferenceEquals(x, y))
+                return 0;
+            if (ReferenceEquals(null, y))
+                return 1;
+            if (ReferenceEquals(null, x))
+                return -1;
+
+            var cmp = -x.RealDisplayWeight.CompareTo(y.RealDisplayWeight);
+            if (cmp != 0)
+                return cmp;
+            return string.Compare(x.ID, y.ID, StringComparison.Ordinal);
+        }
+    }
 }