]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
User accessible playtime (#21242)
authorRepo <47093363+Titian3@users.noreply.github.com>
Sat, 28 Oct 2023 12:01:11 +0000 (01:01 +1300)
committerGitHub <noreply@github.com>
Sat, 28 Oct 2023 12:01:11 +0000 (12:01 +0000)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs [new file with mode: 0644]
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.xaml [new file with mode: 0644]
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.cs [new file with mode: 0644]
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.xaml [new file with mode: 0644]
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs [new file with mode: 0644]
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.xaml [new file with mode: 0644]
Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
Content.Client/Preferences/UI/CharacterSetupGui.xaml
Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
Resources/Locale/en-US/info/playtime-stats.ftl [new file with mode: 0644]
Resources/Locale/en-US/preferences/ui/character-setup-gui.ftl

diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
new file mode 100644 (file)
index 0000000..aff0180
--- /dev/null
@@ -0,0 +1,39 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Info.PlaytimeStats;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlaytimeStatsEntry : ContainerButton
+{
+    public TimeSpan Playtime { get; private set; }  // new TimeSpan property
+
+    public PlaytimeStatsEntry(string role, TimeSpan playtime, StyleBox styleBox)
+    {
+        RobustXamlLoader.Load(this);
+
+        RoleLabel.Text = role;
+        Playtime = playtime;  // store the TimeSpan value directly
+        PlaytimeLabel.Text = ConvertTimeSpanToHoursMinutes(playtime);  // convert to string for display
+        BackgroundColorPanel.PanelOverride = styleBox;
+    }
+
+    private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
+    {
+        var hours = (int)timeSpan.TotalHours;
+        var minutes = timeSpan.Minutes;
+
+        var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
+        return formattedTimeLoc;
+    }
+
+    public void UpdateShading(StyleBoxFlat styleBox)
+    {
+        BackgroundColorPanel.PanelOverride = styleBox;
+    }
+    public string? PlaytimeText => PlaytimeLabel.Text;
+
+    public string? RoleText => RoleLabel.Text;
+}
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.xaml b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.xaml
new file mode 100644 (file)
index 0000000..97a66e5
--- /dev/null
@@ -0,0 +1,20 @@
+<ContainerButton xmlns="https://spacestation14.io"
+                 xmlns:customControls1="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                 EnableAllKeybinds="True">
+    <PanelContainer Name="BackgroundColorPanel"/>
+    <BoxContainer Orientation="Horizontal"
+                  HorizontalExpand="True"
+                  SeparationOverride="4">
+        <Label Name="RoleLabel"
+               SizeFlagsStretchRatio="3"
+               HorizontalExpand="True"
+               ClipText="True"
+               Margin="5,5,5,5"/>
+        <customControls1:VSeparator/>
+        <Label Name="PlaytimeLabel"
+               SizeFlagsStretchRatio="3"
+               HorizontalExpand="True"
+               ClipText="True"
+               Margin="5,5,5,5"/>
+    </BoxContainer>
+</ContainerButton>
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.cs
new file mode 100644 (file)
index 0000000..b005c64
--- /dev/null
@@ -0,0 +1,86 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+
+namespace Content.Client.Info.PlaytimeStats;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlaytimeStatsHeader : ContainerButton
+{
+    public event Action<Header, SortDirection>? OnHeaderClicked;
+    private SortDirection _roleDirection = SortDirection.Ascending;
+    private SortDirection _playtimeDirection = SortDirection.Descending;
+
+    public PlaytimeStatsHeader()
+    {
+        RobustXamlLoader.Load(this);
+
+        RoleLabel.OnKeyBindDown += RoleClicked;
+        PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
+
+        UpdateLabels();
+    }
+
+    public enum Header : byte
+    {
+        Role,
+        Playtime
+    }
+    public enum SortDirection : byte
+    {
+        Ascending,
+        Descending
+    }
+
+    private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
+    {
+        if (args.Function != EngineKeyFunctions.UIClick)
+        {
+            return;
+        }
+
+        switch (header)
+        {
+            case Header.Role:
+                _roleDirection = _roleDirection == SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending;
+                break;
+            case Header.Playtime:
+                _playtimeDirection = _playtimeDirection == SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending;
+                break;
+        }
+
+        UpdateLabels();
+        OnHeaderClicked?.Invoke(header, header == Header.Role ? _roleDirection : _playtimeDirection);
+        args.Handle();
+    }
+    private void UpdateLabels()
+    {
+        RoleLabel.Text = Loc.GetString("ui-playtime-header-role-type") +
+                         (_roleDirection == SortDirection.Ascending ? " ↓" : " ↑");
+        PlaytimeLabel.Text = Loc.GetString("ui-playtime-header-role-time") +
+                             (_playtimeDirection == SortDirection.Ascending ? " ↓" : " ↑");
+    }
+
+    private void RoleClicked(GUIBoundKeyEventArgs args)
+    {
+        HeaderClicked(args, Header.Role);
+    }
+
+    private void PlaytimeClicked(GUIBoundKeyEventArgs args)
+    {
+        HeaderClicked(args, Header.Playtime);
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        base.Dispose(disposing);
+
+        if (disposing)
+        {
+            RoleLabel.OnKeyBindDown -= RoleClicked;
+            PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
+        }
+    }
+}
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.xaml b/Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.xaml
new file mode 100644 (file)
index 0000000..4cf4d8e
--- /dev/null
@@ -0,0 +1,29 @@
+<ContainerButton xmlns="https://spacestation14.io"
+                 xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                 EnableAllKeybinds="True">
+    <PanelContainer Name="BackgroundColorPlaytimePanel" Access="Public"/>
+    <BoxContainer Orientation="Vertical"
+                  HorizontalExpand="True">
+        <BoxContainer Orientation="Horizontal"
+                      HorizontalExpand="True"
+                      SeparationOverride="4">
+            <Label Name="RoleLabel"
+                   SizeFlagsStretchRatio="3"
+                   HorizontalExpand="True"
+                   ClipText="True"
+                   Text="{Loc ui-playtime-header-role-type}"
+                   MouseFilter="Pass"
+                   Margin="5,5,5,5"/>
+            <customControls:VSeparator/>
+            <Label Name="PlaytimeLabel"
+                   SizeFlagsStretchRatio="3"
+                   HorizontalExpand="True"
+                   ClipText="True"
+                   Text="{Loc ui-playtime-header-role-time}"
+                   MouseFilter="Pass"
+                   Margin="5,5,5,5"/>
+        </BoxContainer>
+        <!-- Horizontal Separator -->
+        <customControls:HSeparator/>
+    </BoxContainer>
+</ContainerButton>
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
new file mode 100644 (file)
index 0000000..3b54bf8
--- /dev/null
@@ -0,0 +1,146 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Info.PlaytimeStats;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlaytimeStatsWindow : FancyWindow
+{
+    [Dependency] private readonly JobRequirementsManager _jobRequirementsManager = default!;
+    private ISawmill _sawmill = Logger.GetSawmill("PlaytimeStatsWindow");
+    private readonly Color _altColor = Color.FromHex("#292B38");
+    private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
+    private bool _useAltColor;
+
+    public PlaytimeStatsWindow()
+    {
+        IoCManager.InjectDependencies(this);
+        RobustXamlLoader.Load(this);
+
+        PopulatePlaytimeHeader();
+        PopulatePlaytimeData();
+    }
+
+    private void PopulatePlaytimeHeader()
+    {
+        var header = new PlaytimeStatsHeader();
+        header.OnHeaderClicked += HeaderClicked;
+        header.BackgroundColorPlaytimePanel.PanelOverride = new StyleBoxFlat(_altColor);
+        RolesPlaytimeList.AddChild(header);
+    }
+
+    private void HeaderClicked(PlaytimeStatsHeader.Header header, PlaytimeStatsHeader.SortDirection direction)
+    {
+        switch (header)
+        {
+            case PlaytimeStatsHeader.Header.Role:
+                SortByRole(direction);
+                break;
+            case PlaytimeStatsHeader.Header.Playtime:
+                SortByPlaytime(direction);
+                break;
+        }
+    }
+
+    private void SortByRole(PlaytimeStatsHeader.SortDirection direction)
+    {
+        var header = RolesPlaytimeList.GetChild(0) as PlaytimeStatsHeader;
+
+        var entries = RolesPlaytimeList.Children.OfType<PlaytimeStatsEntry>().ToList();
+
+        RolesPlaytimeList.RemoveAllChildren();
+
+        if (header != null)
+            RolesPlaytimeList.AddChild(header);
+
+        var sortedEntries = (direction == PlaytimeStatsHeader.SortDirection.Ascending)
+            ? entries.OrderBy(entry => entry.RoleText).ToList()
+            : entries.OrderByDescending(entry => entry.RoleText).ToList();
+
+        _useAltColor = false;
+
+        foreach (var entry in sortedEntries)
+        {
+            var styleBox = new StyleBoxFlat { BackgroundColor = _useAltColor ? _altColor : _defaultColor };
+            entry.UpdateShading(styleBox);
+            RolesPlaytimeList.AddChild(entry);
+            _useAltColor ^= true;
+        }
+    }
+
+    private void SortByPlaytime(PlaytimeStatsHeader.SortDirection direction)
+    {
+        var header = RolesPlaytimeList.GetChild(0) as PlaytimeStatsHeader;
+
+        var entries = RolesPlaytimeList.Children.OfType<PlaytimeStatsEntry>().ToList();
+
+        RolesPlaytimeList.RemoveAllChildren();
+
+        if (header != null)
+            RolesPlaytimeList.AddChild(header);
+
+        var sortedEntries = (direction == PlaytimeStatsHeader.SortDirection.Ascending)
+            ? entries.OrderBy(entry => entry.Playtime).ToList()
+            : entries.OrderByDescending(entry => entry.Playtime).ToList();
+
+        _useAltColor = false;
+
+        foreach (var entry in sortedEntries)
+        {
+            var styleBox = new StyleBoxFlat { BackgroundColor = _useAltColor ? _altColor : _defaultColor };
+            entry.UpdateShading(styleBox);
+            RolesPlaytimeList.AddChild(entry);
+            _useAltColor ^= true;
+        }
+    }
+
+
+    private void PopulatePlaytimeData()
+    {
+        var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime();
+
+        var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime);
+        OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime));
+
+        var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles();
+
+        RolesPlaytimeList.RemoveAllChildren();
+        PopulatePlaytimeHeader();
+
+        foreach (var rolePlaytime in rolePlaytimes)
+        {
+            var role = rolePlaytime.Key;
+            var playtime = rolePlaytime.Value;
+            AddRolePlaytimeEntryToTable(Loc.GetString(role), playtime.ToString());
+        }
+    }
+
+    private void AddRolePlaytimeEntryToTable(string role, string playtimeString)
+    {
+        if (TimeSpan.TryParse(playtimeString, out var playtime))
+        {
+            var entry = new PlaytimeStatsEntry(role, playtime,
+                new StyleBoxFlat(_useAltColor ? _altColor : _defaultColor));
+            RolesPlaytimeList.AddChild(entry);
+            _useAltColor ^= true;
+        }
+        else
+        {
+            _sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format.");
+        }
+    }
+
+    private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
+    {
+        var hours = (int) timeSpan.TotalHours;
+        var minutes = timeSpan.Minutes;
+
+        var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
+        return formattedTimeLoc;
+    }
+}
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.xaml b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.xaml
new file mode 100644 (file)
index 0000000..b38394d
--- /dev/null
@@ -0,0 +1,25 @@
+<ui:FancyWindow xmlns="https://spacestation14.io"
+                xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
+                xmlns:pt="clr-namespace:Content.Client.Info.PlaytimeStats"
+                xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+                VerticalExpand="True" HorizontalExpand="True"
+                Title="{Loc ui-playtime-stats-title}"
+                SetSize="600 400">
+    <Control>
+        <BoxContainer Name="statsBox" Orientation="Vertical" Margin="10,10,10,10">
+
+            <!-- Overall Playtime -->
+            <Label Name="OverallPlaytimeLabel" HorizontalExpand="True" Text="{Loc ui-playtime-overall-base}" />
+            <Control MinSize="0 5" />
+
+            <!-- Table for roles -->
+            <ScrollContainer HorizontalExpand="True" VerticalExpand="True">
+                <BoxContainer Orientation="Vertical" Name="RolesPlaytimeList">
+                    <!-- Table Header -->
+                    <pt:PlaytimeStatsHeader Name="ListHeader" />
+                    <customControls:HSeparator />
+                </BoxContainer>
+            </ScrollContainer>
+        </BoxContainer>
+    </Control>
+</ui:FancyWindow>
index c67c759dfdd895bf52f4af3d38f04bc35171e5e4..0929b90a411fc9568de8461b9d5622e9807b31e6 100644 (file)
@@ -1,6 +1,4 @@
 using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
 using Content.Shared.CCVar;
 using Content.Shared.Players;
 using Content.Shared.Players.PlayTimeTracking;
@@ -122,4 +120,24 @@ public sealed class JobRequirementsManager
         reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons));
         return reason == null;
     }
+
+    public TimeSpan FetchOverallPlaytime()
+    {
+        return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
+    }
+
+    public IEnumerable<KeyValuePair<string, TimeSpan>> FetchPlaytimeByRoles()
+    {
+        var jobsToMap = _prototypes.EnumeratePrototypes<JobPrototype>();
+
+        foreach (var job in jobsToMap)
+        {
+            if (_roles.TryGetValue(job.PlayTimeTracker, out var locJobName))
+            {
+                yield return new KeyValuePair<string, TimeSpan>(job.Name, locJobName);
+            }
+        }
+    }
+
+
 }
index 5db8610475fd0c133020dbffbbb793751c4aefb0..9a76029ce0b716655ac18aec6128eb62d4a23718 100644 (file)
                 <Label Text="{Loc 'character-setup-gui-character-setup-label'}"
                        Margin="8 0 0 0" VAlign="Center"
                        StyleClasses="LabelHeadingBigger" />
-                <Button Name="RulesButton" HorizontalExpand="True"
-                        Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
+                <Button Name="StatsButton" HorizontalExpand="True"
+                        Text="{Loc 'character-setup-gui-character-setup-stats-button'}"
                         StyleClasses="ButtonBig"
                         HorizontalAlignment="Right" />
+                <Button Name="RulesButton"
+                        Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
+                        StyleClasses="ButtonBig"/>
                 <Button Name="SaveButton"
                         Access="Public"
                         Text="{Loc 'character-setup-gui-character-setup-save-button'}"
index 138614641437b84a78c8c4c48dac171a042b2fc3..f1052086de6603b53ce11cf017b6373ab8506a70 100644 (file)
@@ -2,6 +2,7 @@ using System.Linq;
 using System.Numerics;
 using Content.Client.Humanoid;
 using Content.Client.Info;
+using Content.Client.Info.PlaytimeStats;
 using Content.Client.Lobby.UI;
 using Content.Client.Resources;
 using Content.Client.Stylesheets;
@@ -80,6 +81,8 @@ namespace Content.Client.Preferences.UI
             UpdateUI();
 
             RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
+
+            StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
             preferencesManager.OnServerDataLoaded += UpdateUI;
         }
 
diff --git a/Resources/Locale/en-US/info/playtime-stats.ftl b/Resources/Locale/en-US/info/playtime-stats.ftl
new file mode 100644 (file)
index 0000000..44ba39c
--- /dev/null
@@ -0,0 +1,10 @@
+# Playtime Stats
+
+ui-playtime-stats-title = User Playtime Stats
+ui-playtime-overall-base = Overall Playtime:
+ui-playtime-overall = Overall Playtime: {$time}
+ui-playtime-first-time = First Time Playing
+ui-playtime-roles = Playtime per Role
+ui-playtime-time-format = {$hours}H {$minutes}M
+ui-playtime-header-role-type = Role
+ui-playtime-header-role-time = Time
index 6644bd62aa6cb26c6b822c3f57b90dd6c828d14c..bd80815e2319de35417d2ab4329ed2906748fe98 100644 (file)
@@ -1,4 +1,5 @@
 character-setup-gui-character-setup-label = Character setup
+character-setup-gui-character-setup-stats-button = Stats
 character-setup-gui-character-setup-rules-button = Rules
 character-setup-gui-character-setup-save-button = Save
 character-setup-gui-character-setup-close-button = Close