User accessible playtime (#21242)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Repo
2023-10-29 01:01:11 +13:00
committed by GitHub
parent 5776a15b81
commit e0f3bf0a06
11 changed files with 384 additions and 4 deletions

View 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;
}

View 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>

View 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;
}
}
}

View 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>

View 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;
}
}

View 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>

View 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);
}
}
}
}

View File

@@ -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'}"

View 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;
}

View 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

View 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