User accessible playtime (#21242)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
39
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
Normal file
39
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
Normal file
@@ -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;
|
||||
}
|
||||
20
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.xaml
Normal file
20
Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.xaml
Normal file
@@ -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>
|
||||
86
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.cs
Normal file
86
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.xaml
Normal file
29
Content.Client/Info/PlaytimeStats/PlaytimeStatsHeader.xaml
Normal file
@@ -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>
|
||||
146
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
Normal file
146
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
25
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.xaml
Normal file
25
Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.xaml
Normal file
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
<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'}"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
10
Resources/Locale/en-US/info/playtime-stats.ftl
Normal file
10
Resources/Locale/en-US/info/playtime-stats.ftl
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user