]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add RGA/RSI to Credits (#36704)
authorThomas <87614336+Aeshus@users.noreply.github.com>
Mon, 21 Apr 2025 10:24:44 +0000 (06:24 -0400)
committerGitHub <noreply@github.com>
Mon, 21 Apr 2025 10:24:44 +0000 (12:24 +0200)
* Add RGA and RSI to Credits

* Move to thread + add directory field

Content.Client/Credits/CreditsWindow.xaml
Content.Client/Credits/CreditsWindow.xaml.cs
Resources/Locale/en-US/credits/credits-window.ftl

index 2cb6f7ab9298575306db689c9a4bf26d5b729e29..b7bc2329c12feb0721f96b6f9c357db6cb3929d3 100644 (file)
@@ -1,7 +1,7 @@
 <DefaultWindow xmlns="https://spacestation14.io"
-            Title="{Loc 'credits-window-title'}"
-            SetSize="650 650" >
-    <TabContainer>
+               Title="{Loc 'credits-window-title'}"
+               SetSize="650 650">
+    <TabContainer Name="MasterTabContainer">
         <ScrollContainer Name="Ss14ContributorsTab"
                          HScrollEnabled="False">
             <BoxContainer Name="Ss14ContributorsContainer"
                 <!-- Licenses get added here by code -->
             </BoxContainer>
         </ScrollContainer>
+        <ScrollContainer Name="AttributionsTab"
+                         HScrollEnabled="False">
+            <BoxContainer Name="AttributionsContainer"
+                          Orientation="Vertical"
+                          Margin="2 2 0 0" />
+        </ScrollContainer>
     </TabContainer>
 </DefaultWindow>
index ba240209533a2fb68cd71f57a3d3d026103f46fb..64f5479421c4314e5b71a36db81b51e635004e11 100644 (file)
-using System.Collections.Generic;
 using System.Linq;
 using System.Numerics;
+using System.Threading.Tasks;
 using Content.Client.Stylesheets;
 using Content.Shared.CCVar;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Credits;
-using Robust.Client.ResourceManagement;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared.Configuration;
 using Robust.Shared.ContentPack;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Serialization.Markdown;
+using Robust.Shared.Serialization.Markdown.Mapping;
+using Robust.Shared.Serialization.Markdown.Sequence;
 using Robust.Shared.Utility;
 using YamlDotNet.RepresentationModel;
 using static Robust.Client.UserInterface.Controls.BoxContainer;
 
-namespace Content.Client.Credits
+namespace Content.Client.Credits;
+
+[GenerateTypedNameReferences]
+public sealed partial class CreditsWindow : DefaultWindow
 {
-    [GenerateTypedNameReferences]
-    public sealed partial class CreditsWindow : DefaultWindow
+    [Dependency] private readonly IResourceManager _resourceManager = default!;
+    [Dependency] private readonly IConfigurationManager _cfg = default!;
+    [Dependency] private readonly ISerializationManager _serialization = default!;
+    [Dependency] private readonly IPrototypeManager _protoManager = default!;
+    [Dependency] private readonly ILocalizationManager _loc = default!;
+
+    private static readonly Dictionary<string, int> PatronTierPriority = new()
     {
-        [Dependency] private readonly IResourceManager _resourceManager = default!;
-        [Dependency] private readonly IConfigurationManager _cfg = default!;
+        ["Nuclear Operative"] = 1,
+        ["Syndicate Agent"] = 2,
+        ["Revolutionary"] = 3,
+    };
+
+    private readonly List<FormattedMessage> _attributions = [];
+    private readonly ISawmill _sawmill = Logger.GetSawmill("Credits");
+
+    private const int AttributionsSourcesPerPage = 50;
+
+    public CreditsWindow()
+    {
+        IoCManager.InjectDependencies(this);
+        RobustXamlLoader.Load(this);
+
+        TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
+        TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
+        TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
+        TabContainer.SetTabTitle(AttributionsTab, Loc.GetString("credits-window-attributions-tab"));
 
-        private static readonly Dictionary<string, int> PatronTierPriority = new()
+        _protoManager.PrototypesReloaded += _ =>
         {
-            ["Nuclear Operative"] = 1,
-            ["Syndicate Agent"] = 2,
-            ["Revolutionary"] = 3
+            _attributions.Clear();
         };
 
-        public CreditsWindow()
-        {
-            IoCManager.InjectDependencies(this);
-            RobustXamlLoader.Load(this);
+        MasterTabContainer.OnTabChanged += OnTabChanged;
 
-            TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
-            TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
-            TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
+        PopulateContributors(Ss14ContributorsContainer);
+    }
 
+    /// <summary>
+    /// Only populates the tab when they are selected, which reduces lagspike when not looking at attributions.
+    /// </summary>
+    private void OnTabChanged(int tab)
+    {
+        if (tab == Ss14ContributorsTab.GetPositionInParent())
             PopulateContributors(Ss14ContributorsContainer);
+        else if (tab == PatronsTab.GetPositionInParent())
             PopulatePatrons(PatronsContainer);
+        else if (tab == LicensesTab.GetPositionInParent())
             PopulateLicenses(LicensesContainer);
+        else if (tab == AttributionsTab.GetPositionInParent())
+            PopulateAttributions(AttributionsContainer, 0);
+    }
+
+    private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
+    {
+        attributionsContainer.DisposeAllChildren();
+
+        if (_attributions.Count == 0)
+        {
+            var rsi = await CollectRSiAttributions();
+            var rga = await CollectRgaAttributions();
+
+            _attributions.AddRange(rsi);
+            _attributions.AddRange(rga);
+        }
+
+        foreach (var message in _attributions.Skip(count).Take(AttributionsSourcesPerPage))
+        {
+            var rich = new RichTextLabel();
+            rich.SetMessage(message);
+            attributionsContainer.AddChild(rich);
         }
 
-        private void PopulateLicenses(BoxContainer licensesContainer)
+        var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal };
+
+        var previousPageButton = new Button { Text = "Previous Page" };
+        previousPageButton.OnPressed +=
+            _ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage);
+
+        var nextPageButton = new Button { Text = "Next Page" };
+        nextPageButton.OnPressed +=
+            _ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage);
+
+        if (count - AttributionsSourcesPerPage >= 0)
+            container.AddChild(previousPageButton);
+        if (count + AttributionsSourcesPerPage < _attributions.Count)
+            container.AddChild(nextPageButton);
+
+        attributionsContainer.AddChild(container);
+    }
+
+    private Task<List<FormattedMessage>> CollectRSiAttributions()
+    {
+        return Task.Run(() =>
         {
-            foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
+            var rsiStreams = _resourceManager.ContentFindFiles("/Textures/")
+                .Where(p => p.ToString().EndsWith(".rsi/meta.json"));
+
+            var attrs = new List<FormattedMessage>();
+
+            foreach (var stream in rsiStreams)
             {
-                licensesContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = entry.Name});
+                try
+                {
+                    var m = new FormattedMessage();
+
+                    var yamlStream = _resourceManager.ContentFileReadYaml(stream);
+
+                    if (yamlStream.Documents[0].RootNode.ToDataNode() is not MappingDataNode map)
+                        throw new Exception("meta.json is not a mapping.");
+
+                    if (!map.TryGet("copyright", out var copyrightNode))
+                        throw new Exception("Missing the copyright field.");
 
-                // We split these line by line because otherwise
-                // the LGPL causes Clyde to go out of bounds in the rendering code.
-                foreach (var line in entry.License.Split("\n"))
+                    if (!map.TryGet("states", out var statesNode))
+                        throw new Exception("Missing the states field.");
+
+                    if (statesNode is not SequenceDataNode states)
+                        throw new Exception("Missing a list of states.");
+
+                    var copyright = copyrightNode.ToString();
+                    var files = states.Select(n => (MappingDataNode)n)
+                        .Select(n => n.Get("name") + ".png");
+
+                    m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
+                        ("directory", stream.Directory.ToString())));
+                    m.AddText("\n");
+                    m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
+                        ("files", string.Join(", ", files))));
+                    m.AddText("\n");
+                    m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
+                        ("copyright", copyright)));
+                    m.AddText("\n");
+
+                    attrs.Add(m);
+                }
+                catch (Exception e)
                 {
-                    licensesContainer.AddChild(new Label {Text = line, FontColorOverride = new Color(200, 200, 200)});
+                    var m = new FormattedMessage();
+                    m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
+                        ("file", stream.ToString())));
+                    m.AddText("\n");
+                    _sawmill.Error($"{stream.ToString()}\n{e}");
+                    attrs.Add(m);
                 }
             }
-        }
 
-        private void PopulatePatrons(BoxContainer patronsContainer)
+            return attrs;
+        });
+    }
+
+    private Task<List<FormattedMessage>> CollectRgaAttributions()
+    {
+        return Task.Run(() =>
         {
-            var patrons = LoadPatrons();
+            var rgaStreams = _resourceManager.ContentFindFiles("/")
+                .Where(p => p.Filename == "attributions.yml");
 
-            // Do not show "become a patron" button on Steam builds
-            // since Patreon violates Valve's rules about alternative storefronts.
-            var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
-            if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
+            var attrs = new List<FormattedMessage>();
+
+            foreach (var stream in rgaStreams)
             {
-                Button patronButton;
-                patronsContainer.AddChild(patronButton = new Button
+                try
                 {
-                    Text = Loc.GetString("credits-window-become-patron-button"),
-                    HorizontalAlignment = HAlignment.Center
-                });
+                    var yamlStream = _resourceManager.ContentFileReadYaml(stream);
 
-                patronButton.OnPressed +=
-                    _ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
-            }
+                    if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
+                        throw new Exception("Attributions file is not a list of attributions.");
 
-            var first = true;
-            foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
-            {
-                if (!first)
-                {
-                    patronsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
-                }
+                    foreach (var attribution in sequence.Sequence)
+                    {
+                        var m = new FormattedMessage();
+
+                        if (attribution is not MappingDataNode map)
+                            throw new Exception("Attribution is not a mapping.");
+
+                        if (!map.TryGet("files", out var filesNode))
+                            throw new Exception("Attribution does not list files.");
+
+                        if (!map.TryGet("copyright", out var copyrightNode))
+                            throw new Exception("Attribution does not copyright.");
+
+                        if (!map.TryGet("license", out var licenseNode))
+                            throw new Exception("Attribution does not identify a license.");
 
-                first = false;
-                patronsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = $"{tier.Key}"});
+                        if (!map.TryGet("source", out var sourceNode))
+                            throw new Exception("Attribution does not identify a source.");
 
-                var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
+                        var files = _serialization.Read<string[]>(filesNode, notNullableOverride: true);
+                        var copyright = copyrightNode.ToString();
+                        var license = licenseNode.ToString();
+                        var source = sourceNode.ToString();
 
-                var label = new RichTextLabel();
-                label.SetMessage(msg);
+                        m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
+                            ("directory", stream.Directory.ToString())));
+                        m.AddText("\n");
+                        m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
+                            ("files", string.Join(", ", files))));
+                        m.AddText("\n");
+                        m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
+                            ("copyright", copyright)));
+                        m.AddText("\n");
+                        m.AddMarkupPermissive(
+                            _loc.GetString("credits-window-attributions-license", ("license", license)));
+                        m.AddText("\n");
+                        m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-source", ("source", source)));
+                        m.AddText("\n");
 
-                patronsContainer.AddChild(label);
+                        attrs.Add(m);
+                    }
+                }
+                catch (Exception e)
+                {
+                    var m = new FormattedMessage();
+                    m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
+                        ("file", stream.ToString())));
+                    m.AddText("\n");
+                    _sawmill.Error($"{stream.ToString()}\n{e}");
+                    attrs.Add(m);
+                }
             }
-        }
 
-        private IEnumerable<PatronEntry> LoadPatrons()
+            return attrs;
+        });
+    }
+
+    private void PopulateLicenses(BoxContainer licensesContainer)
+    {
+        foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
         {
-            var yamlStream = _resourceManager.ContentFileReadYaml(new ("/Credits/Patrons.yml"));
-            var sequence = (YamlSequenceNode) yamlStream.Documents[0].RootNode;
+            licensesContainer.AddChild(new Label
+                { StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = entry.Name });
 
-            return sequence
-                .Cast<YamlMappingNode>()
-                .Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
+            // We split these line by line because otherwise
+            // the LGPL causes Clyde to go out of bounds in the rendering code.
+            foreach (var line in entry.License.Split("\n"))
+            {
+                licensesContainer.AddChild(new Label { Text = line, FontColorOverride = new Color(200, 200, 200) });
+            }
         }
+    }
 
-        private void PopulateContributors(BoxContainer ss14ContributorsContainer)
-        {
-            Button contributeButton;
+    private void PopulatePatrons(BoxContainer patronsContainer)
+    {
+        var patrons = LoadPatrons();
 
-            ss14ContributorsContainer.AddChild(new BoxContainer
+        // Do not show "become a patron" button on Steam builds
+        // since Patreon violates Valve's rules about alternative storefronts.
+        var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
+        if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
+        {
+            Button patronButton;
+            patronsContainer.AddChild(patronButton = new Button
             {
-                Orientation = LayoutOrientation.Horizontal,
+                Text = Loc.GetString("credits-window-become-patron-button"),
                 HorizontalAlignment = HAlignment.Center,
-                SeparationOverride = 20,
-                Children =
-                {
-                    new Label {Text = Loc.GetString("credits-window-contributor-encouragement-label") },
-                    (contributeButton = new Button {Text = Loc.GetString("credits-window-contribute-button")})
-                }
             });
 
-            var first = true;
+            patronButton.OnPressed +=
+                _ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
+        }
 
-            void AddSection(string title, string path, bool markup = false)
-            {
-                if (!first)
-                {
-                    ss14ContributorsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
-                }
+        var first = true;
+        foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
+        {
+            if (!first)
+                patronsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
 
-                first = false;
-                ss14ContributorsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = title});
+            first = false;
+            patronsContainer.AddChild(new Label
+                { StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = $"{tier.Key}" });
 
-                var label = new RichTextLabel();
-                var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
-                if (markup)
-                {
-                    label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
-                }
-                else
-                {
-                    label.SetMessage(text);
-                }
+            var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
 
-                ss14ContributorsContainer.AddChild(label);
-            }
+            var label = new RichTextLabel();
+            label.SetMessage(msg);
 
-            AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
-            AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
-            AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
-            AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
+            patronsContainer.AddChild(label);
+        }
+    }
 
-            var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
+    private IEnumerable<PatronEntry> LoadPatrons()
+    {
+        var yamlStream = _resourceManager.ContentFileReadYaml(new ResPath("/Credits/Patrons.yml"));
+        var sequence = (YamlSequenceNode)yamlStream.Documents[0].RootNode;
 
-            contributeButton.OnPressed += _ =>
-                IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
+        return sequence
+            .Cast<YamlMappingNode>()
+            .Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
+    }
 
-            if (linkGithub == "")
-                contributeButton.Visible = false;
-        }
+    private void PopulateContributors(BoxContainer ss14ContributorsContainer)
+    {
+        Button contributeButton;
 
-        private sealed class PatronEntry
+        ss14ContributorsContainer.AddChild(new BoxContainer
         {
-            public string Name { get; }
-            public string Tier { get; }
-
-            public PatronEntry(string name, string tier)
+            Orientation = LayoutOrientation.Horizontal,
+            HorizontalAlignment = HAlignment.Center,
+            SeparationOverride = 20,
+            Children =
             {
-                Name = name;
-                Tier = tier;
-            }
+                new Label { Text = Loc.GetString("credits-window-contributor-encouragement-label") },
+                (contributeButton = new Button { Text = Loc.GetString("credits-window-contribute-button") }),
+            },
+        });
+
+        var first = true;
+
+        void AddSection(string title, string path, bool markup = false)
+        {
+            if (!first)
+                ss14ContributorsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
+
+            first = false;
+            ss14ContributorsContainer.AddChild(new Label
+                { StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = title });
+
+            var label = new RichTextLabel();
+            var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
+            if (markup)
+                label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
+            else
+                label.SetMessage(text);
+
+            ss14ContributorsContainer.AddChild(label);
+        }
+
+        AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
+        AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
+        AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
+        AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
+
+        var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
+
+        contributeButton.OnPressed += _ =>
+            IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
+
+        if (linkGithub == "")
+            contributeButton.Visible = false;
+    }
+
+    private sealed class PatronEntry
+    {
+        public string Name { get; }
+        public string Tier { get; }
+
+        public PatronEntry(string name, string tier)
+        {
+            Name = name;
+            Tier = tier;
         }
     }
 }
index 1b9636367b670d77702e758cce4de1a0d084a78c..4bd7f7a426539c086ea047c7ac4022a79ba5dba2 100644 (file)
@@ -2,10 +2,18 @@ credits-window-title = Credits
 credits-window-patrons-tab = Patrons
 credits-window-ss14contributorslist-tab = Credits
 credits-window-licenses-tab = Open Source Licenses
+credits-window-attributions-tab = Attributions
 credits-window-become-patron-button = Become a Patron
 credits-window-contributor-encouragement-label = Want to get on this list?
 credits-window-contribute-button = Contribute!
 credits-window-contributors-section-title = Space Station 14 Contributors
 credits-window-codebases-section-title = Space Station 13 Codebases
 credits-window-original-remake-team-section-title = Original Space Station 13 Remake Team
-credits-window-special-thanks-section-title = Special Thanks
\ No newline at end of file
+credits-window-special-thanks-section-title = Special Thanks
+
+credits-window-attributions-directory = [color=white]Directory:[/color] {$directory}
+credits-window-attributions-files = [color=white]Files:[/color] {$files}
+credits-window-attributions-copyright = [color=white]Copyright:[/color] {$copyright}
+credits-window-attributions-license = [color=white]License:[/color] {$license}
+credits-window-attributions-source = [color=white]Source:[/color] {$source}
+credits-window-attributions-failed = [color=red]Failed to read file:[/color] {$file}