Skip to content

Commit 233ac49

Browse files
Redesign playtime stats UI with server tabs and merged tracking support (#4045)
<!-- IT'S NOT WIZDENS REPO, IF YOU WANT TO ADD YOUR CHANGES ON ALL SERVERS, CREATE PR TO WIZDENS REPO --> ## Short description Playtime sync fix and display of other servers in the playtime window. ## Media (Video/Screenshots) <!-- If your PR contains in-game changes you must provide screenshots/videos of the changes. --> ## Checks <!-- check boxes for faster reviewing of your PR --> - [x] I do not require assistance to complete the PR. - [x] Before posting/requesting review of a PR, I have verified that the changes work. - [x] I have added screenshots/videos of the changes, or this PR does not change in-game mechanics. - [x] I affirm that my changes are licensed under the [MIT License](https://github.com/ss14Starlight/space-station-14/blob/Starlight/LICENSE.TXT) and grant permission for use in this repository under its conditions. **Changelog** :cl: STARLIGHT TEAM - add: The playtime modal has been reworked. It now shows playtime from the other servers too. - fix: The panic bunker now correctly accepts playtime from other servers.
1 parent 66cde87 commit 233ac49

7 files changed

Lines changed: 291 additions & 138 deletions

File tree

Lines changed: 163 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,219 @@
11
using System.Linq;
2-
using System.Text.RegularExpressions;
32
using Content.Client.Players.PlayTimeTracking;
43
using Content.Client.UserInterface.Controls;
4+
using Content.Shared.Roles;
5+
using Content.Shared.Players.PlayTimeTracking;
56
using Robust.Client.AutoGenerated;
67
using Robust.Client.Graphics;
8+
using Robust.Client.UserInterface;
9+
using Robust.Client.UserInterface.Controls;
710
using Robust.Client.UserInterface.XAML;
8-
//starlight
9-
using Robust.Shared.Prototypes;
1011

1112
namespace Content.Client.Info.PlaytimeStats;
1213

14+
//starlight start
1315
[GenerateTypedNameReferences]
1416
public sealed partial class PlaytimeStatsWindow : FancyWindow
1517
{
1618
[Dependency] private readonly JobRequirementsManager _jobRequirementsManager = default!;
17-
[Dependency] private readonly IPrototypeManager _prototypes = default!;
18-
private ISawmill _sawmill = Logger.GetSawmill("PlaytimeStatsWindow");
19+
[Dependency] private readonly ILocalizationManager _loc = default!;
1920
private readonly Color _altColor = Color.FromHex("#292B38");
2021
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
2122
private readonly Color _antagColor = Color.FromHex("#fe7676");
2223
private readonly Color _ghostColor = Color.FromHex("#c996e0");
23-
private bool _useAltColor;
2424

2525
public PlaytimeStatsWindow()
2626
{
2727
IoCManager.InjectDependencies(this);
2828
RobustXamlLoader.Load(this);
2929

30-
PopulatePlaytimeHeader();
31-
PopulatePlaytimeData();
30+
BuildTabs();
3231
}
3332

34-
private void PopulatePlaytimeHeader()
33+
private void BuildTabs()
3534
{
36-
var header = new PlaytimeStatsHeader();
37-
header.OnHeaderClicked += HeaderClicked;
38-
header.BackgroundColorPlaytimePanel.PanelOverride = new StyleBoxFlat(_altColor);
39-
RolesPlaytimeList.AddChild(header);
40-
}
35+
ServerTabs.RemoveAllChildren();
36+
37+
var currentServer = _jobRequirementsManager.GetCurrentServerName();
38+
39+
// Tab 1: merged playtime
40+
var mergedRolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles().ToList();
41+
var mergedAntagPlaytimes = _jobRequirementsManager.FetchPlaytimeByAntags().ToList();
42+
var mergedTab = BuildPlaytimeTab(
43+
_jobRequirementsManager.FetchOverallPlaytime(),
44+
mergedRolePlaytimes,
45+
_jobRequirementsManager.FetchPlaytimeByDepartments(),
46+
mergedAntagPlaytimes,
47+
_jobRequirementsManager.FetchPlaytimeMiscellaneous(mergedRolePlaytimes, mergedAntagPlaytimes));
48+
ServerTabs.AddChild(mergedTab);
49+
TabContainer.SetTabTitle(mergedTab, _loc.GetString("ui-playtime-tab-merged"));
50+
51+
// Tab 2: current server (_originalRoles)
52+
var originalRoles = _jobRequirementsManager.GetOriginalRoles();
53+
if (originalRoles.Count > 0)
54+
{
55+
var currentTab = BuildServerTab(originalRoles);
56+
ServerTabs.AddChild(currentTab);
57+
TabContainer.SetTabTitle(currentTab, _loc.GetString("ui-playtime-tab-current") + " ★");
58+
}
4159

42-
private void HeaderClicked(PlaytimeStatsHeader.Header header, PlaytimeStatsHeader.SortDirection direction)
43-
{
44-
switch (header)
60+
// Remaining tabs: NullLink servers grouped by project prefix
61+
var rolesPerServer = _jobRequirementsManager.GetRolesPerServer();
62+
var groups = rolesPerServer
63+
.GroupBy(kv =>
64+
{
65+
var dotIdx = kv.Key.IndexOf('.');
66+
return dotIdx >= 0 ? kv.Key[..dotIdx] : kv.Key;
67+
})
68+
.OrderBy(g => g.Key);
69+
70+
foreach (var group in groups)
4571
{
46-
case PlaytimeStatsHeader.Header.Role:
47-
SortByRole(direction);
48-
break;
49-
case PlaytimeStatsHeader.Header.Playtime:
50-
SortByPlaytime(direction);
51-
break;
72+
var servers = group.ToList();
73+
74+
if (servers.Count == 1)
75+
{
76+
var (serverName, serverRoles) = servers[0];
77+
var tab = BuildServerTab(serverRoles);
78+
ServerTabs.AddChild(tab);
79+
TabContainer.SetTabTitle(tab, serverName);
80+
}
81+
else
82+
{
83+
var innerTabs = new TabContainer
84+
{
85+
HorizontalExpand = true,
86+
VerticalExpand = true,
87+
};
88+
89+
foreach (var (serverName, serverRoles) in servers)
90+
{
91+
var tab = BuildServerTab(serverRoles);
92+
innerTabs.AddChild(tab);
93+
94+
var dotIdx = serverName.IndexOf('.');
95+
var subTitle = dotIdx >= 0 ? serverName[(dotIdx + 1)..] : serverName;
96+
TabContainer.SetTabTitle(tab, subTitle);
97+
}
98+
99+
ServerTabs.AddChild(innerTabs);
100+
TabContainer.SetTabTitle(innerTabs, group.Key);
101+
}
52102
}
53103
}
54104

55-
private void SortByRole(PlaytimeStatsHeader.SortDirection direction)
105+
private BoxContainer BuildServerTab(Dictionary<string, TimeSpan> serverRoles)
56106
{
57-
var header = RolesPlaytimeList.GetChild(0) as PlaytimeStatsHeader;
107+
var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles(serverRoles).ToList();
108+
var departmentPlaytimes = _jobRequirementsManager.FetchPlaytimeByDepartments(serverRoles);
109+
var antagPlaytimes = _jobRequirementsManager.FetchPlaytimeByAntags(serverRoles).ToList();
110+
var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime(serverRoles);
111+
var miscPlaytimes = _jobRequirementsManager.FetchPlaytimeMiscellaneous(serverRoles, rolePlaytimes, antagPlaytimes);
58112

59-
var entries = RolesPlaytimeList.Children.OfType<PlaytimeStatsEntry>().ToList();
60-
61-
RolesPlaytimeList.RemoveAllChildren();
62-
63-
if (header != null)
64-
RolesPlaytimeList.AddChild(header);
113+
return BuildPlaytimeTab(overallPlaytime, rolePlaytimes, departmentPlaytimes, antagPlaytimes, miscPlaytimes);
114+
}
65115

66-
var sortedEntries = (direction == PlaytimeStatsHeader.SortDirection.Ascending)
67-
? entries.OrderBy(entry => entry.RoleText).ToList()
68-
: entries.OrderByDescending(entry => entry.RoleText).ToList();
116+
private BoxContainer BuildPlaytimeTab(
117+
TimeSpan overallPlaytime,
118+
List<KeyValuePair<JobPrototype, TimeSpan>> rolePlaytimes,
119+
IEnumerable<KeyValuePair<DepartmentPrototype, TimeSpan>> departmentPlaytimes,
120+
IEnumerable<KeyValuePair<AntagPrototype, TimeSpan>> antagPlaytimes,
121+
IEnumerable<KeyValuePair<PlayTimeTrackerPrototype, TimeSpan>> miscPlaytimes)
122+
{
123+
var container = new BoxContainer
124+
{
125+
Orientation = BoxContainer.LayoutOrientation.Vertical,
126+
HorizontalExpand = true,
127+
VerticalExpand = true,
128+
};
69129

70-
_useAltColor = false;
130+
// Overall label
131+
var overallLabel = new Label
132+
{
133+
HorizontalExpand = true,
134+
Text = _loc.GetString("ui-playtime-overall", ("time", overallPlaytime)),
135+
};
136+
container.AddChild(overallLabel);
137+
container.AddChild(new Control { MinSize = new System.Numerics.Vector2(0, 5) });
138+
139+
// Scrollable list
140+
var scrollContainer = new ScrollContainer
141+
{
142+
HorizontalExpand = true,
143+
VerticalExpand = true,
144+
};
71145

72-
foreach (var entry in sortedEntries)
146+
var rolesListBox = new BoxContainer
73147
{
74-
var styleBox = new StyleBoxFlat { BackgroundColor = _useAltColor ? _altColor : _defaultColor };
75-
entry.UpdateShading(styleBox);
76-
RolesPlaytimeList.AddChild(entry);
77-
_useAltColor ^= true;
78-
}
79-
}
148+
Orientation = BoxContainer.LayoutOrientation.Vertical,
149+
HorizontalExpand = true,
150+
};
80151

81-
private void SortByPlaytime(PlaytimeStatsHeader.SortDirection direction)
82-
{
83-
var header = RolesPlaytimeList.GetChild(0) as PlaytimeStatsHeader;
152+
// Header
153+
var header = new PlaytimeStatsHeader();
154+
header.OnHeaderClicked += (h, dir) => SortList(rolesListBox, h, dir);
155+
header.BackgroundColorPlaytimePanel.PanelOverride = new StyleBoxFlat(_altColor);
156+
rolesListBox.AddChild(header);
84157

85-
var entries = RolesPlaytimeList.Children.OfType<PlaytimeStatsEntry>().ToList();
158+
var useAltColor = false;
86159

87-
RolesPlaytimeList.RemoveAllChildren();
160+
// Roles
161+
foreach (var rolePlaytime in rolePlaytimes)
162+
AddEntry(rolesListBox, _loc.GetString(rolePlaytime.Key.Name), rolePlaytime.Value, null, ref useAltColor);
88163

89-
if (header != null)
90-
RolesPlaytimeList.AddChild(header);
164+
// Departments
165+
foreach (var departmentPlaytime in departmentPlaytimes)
166+
AddEntry(rolesListBox, _loc.GetString(departmentPlaytime.Key.Name), departmentPlaytime.Value, departmentPlaytime.Key.Color, ref useAltColor);
91167

92-
var sortedEntries = (direction == PlaytimeStatsHeader.SortDirection.Ascending)
93-
? entries.OrderBy(entry => entry.Playtime).ToList()
94-
: entries.OrderByDescending(entry => entry.Playtime).ToList();
168+
// Antags
169+
foreach (var antagPlaytime in antagPlaytimes)
170+
AddEntry(rolesListBox, _loc.GetString(antagPlaytime.Key.Name), antagPlaytime.Value, _antagColor, ref useAltColor);
95171

96-
_useAltColor = false;
172+
// Misc
173+
foreach (var miscPlaytime in miscPlaytimes)
174+
AddEntry(rolesListBox, _loc.GetString(miscPlaytime.Key.Name), miscPlaytime.Value, _ghostColor, ref useAltColor);
97175

98-
foreach (var entry in sortedEntries)
99-
{
100-
var styleBox = new StyleBoxFlat { BackgroundColor = _useAltColor ? _altColor : _defaultColor };
101-
entry.UpdateShading(styleBox);
102-
RolesPlaytimeList.AddChild(entry);
103-
_useAltColor ^= true;
104-
}
176+
scrollContainer.AddChild(rolesListBox);
177+
container.AddChild(scrollContainer);
178+
return container;
105179
}
106180

107-
108-
private void PopulatePlaytimeData()
181+
private void AddEntry(BoxContainer list, string role, TimeSpan playtime, Color? textColor, ref bool useAltColor)
109182
{
110-
var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime();
111-
112-
OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", overallPlaytime));
113-
114-
// Starlight BEGIN
115-
var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles().ToList();
116-
var departmentPlaytimes = _jobRequirementsManager.FetchPlaytimeByDepartments();
117-
var antagPlaytimes = _jobRequirementsManager.FetchPlaytimeByAntags();
118-
var miscellaneousPlaytimes = _jobRequirementsManager.FetchPlaytimeMiscellaneous(rolePlaytimes, antagPlaytimes);
119-
// Starlight END
183+
var entry = new PlaytimeStatsEntry(role, playtime, textColor,
184+
new StyleBoxFlat(useAltColor ? _altColor : _defaultColor));
185+
list.AddChild(entry);
186+
useAltColor ^= true;
187+
}
120188

121-
RolesPlaytimeList.RemoveAllChildren();
122-
PopulatePlaytimeHeader();
189+
private void SortList(BoxContainer list, PlaytimeStatsHeader.Header header, PlaytimeStatsHeader.SortDirection direction)
190+
{
191+
var headerControl = list.Children.OfType<PlaytimeStatsHeader>().FirstOrDefault();
192+
var entries = list.Children.OfType<PlaytimeStatsEntry>().ToList();
123193

124-
foreach (var rolePlaytime in rolePlaytimes)
125-
{
126-
var role = rolePlaytime.Key;
127-
var playtime = rolePlaytime.Value;
128-
AddRolePlaytimeEntryToTable(Loc.GetString(role.Name), playtime.ToString()); //starlight edit
129-
}
194+
list.RemoveAllChildren();
130195

131-
//starlight
132-
foreach (var departmentPlaytime in departmentPlaytimes)
133-
{
134-
var department = departmentPlaytime.Key;
135-
var playtime = departmentPlaytime.Value;
136-
AddRolePlaytimeEntryToTable(Loc.GetString(department.Name), playtime.ToString(), textColor: department.Color); //starlight edit
137-
}
138-
foreach (var antagPlaytime in antagPlaytimes)
139-
{
140-
AddRolePlaytimeEntryToTable(Loc.GetString(antagPlaytime.Key.Name), antagPlaytime.Value.ToString(), textColor: _antagColor);
141-
}
142-
foreach (var miscellaneousPlaytime in miscellaneousPlaytimes)
143-
{
144-
var role = miscellaneousPlaytime.Key;
145-
var playtime = miscellaneousPlaytime.Value;
146-
AddRolePlaytimeEntryToTable(Loc.GetString(role.Name), playtime.ToString(), textColor: _ghostColor);
147-
}
148-
//starlight end
149-
}
196+
if (headerControl != null)
197+
list.AddChild(headerControl);
150198

151-
private void AddRolePlaytimeEntryToTable(string role, string playtimeString, Color? textColor = null) //starlight edit
152-
{
153-
if (TimeSpan.TryParse(playtimeString, out var playtime))
199+
var sortedEntries = header switch
154200
{
155-
var entry = new PlaytimeStatsEntry(role, playtime, textColor, //starlight edit
156-
new StyleBoxFlat(_useAltColor ? _altColor : _defaultColor));
157-
RolesPlaytimeList.AddChild(entry);
158-
_useAltColor ^= true;
159-
}
160-
else
201+
PlaytimeStatsHeader.Header.Role => direction == PlaytimeStatsHeader.SortDirection.Ascending
202+
? [.. entries.OrderBy(e => e.RoleText)]
203+
: [.. entries.OrderByDescending(e => e.RoleText)],
204+
PlaytimeStatsHeader.Header.Playtime => direction == PlaytimeStatsHeader.SortDirection.Ascending
205+
? [.. entries.OrderBy(e => e.Playtime)]
206+
: [.. entries.OrderByDescending(e => e.Playtime)],
207+
_ => entries,
208+
};
209+
210+
var useAlt = false;
211+
foreach (var entry in sortedEntries)
161212
{
162-
_sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format.");
213+
entry.UpdateShading(new StyleBoxFlat(useAlt ? _altColor : _defaultColor));
214+
list.AddChild(entry);
215+
useAlt ^= true;
163216
}
164217
}
165218
}
219+
//starlight end
Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
1+
<!-- starlight start -->
12
<ui:FancyWindow xmlns="https://spacestation14.io"
23
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
34
xmlns:pt="clr-namespace:Content.Client.Info.PlaytimeStats"
45
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
56
VerticalExpand="True" HorizontalExpand="True"
67
Title="{Loc ui-playtime-stats-title}"
7-
SetSize="600 400">
8+
SetSize="650 450">
89
<Control>
910
<BoxContainer Name="statsBox" Orientation="Vertical" Margin="10,10,10,10">
10-
11-
<!-- Overall Playtime -->
12-
<Label Name="OverallPlaytimeLabel" HorizontalExpand="True" Text="{Loc ui-playtime-overall-base}" />
13-
<Control MinSize="0 5" />
14-
15-
<!-- Table for roles -->
16-
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
17-
<BoxContainer Orientation="Vertical" Name="RolesPlaytimeList">
18-
<!-- Table Header -->
19-
<pt:PlaytimeStatsHeader Name="ListHeader" />
20-
<customControls:HSeparator />
21-
</BoxContainer>
22-
</ScrollContainer>
11+
<TabContainer Name="ServerTabs" VerticalExpand="True" HorizontalExpand="True" />
2312
</BoxContainer>
2413
</Control>
2514
</ui:FancyWindow>
15+
<!-- starlight end -->

0 commit comments

Comments
 (0)