]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Playtime Reminders - Raising awareness of addiction by highlighting excessive playtim...
authordeathride58 <deathride58@users.noreply.github.com>
Thu, 19 Jun 2025 01:06:26 +0000 (21:06 -0400)
committerGitHub <noreply@github.com>
Thu, 19 Jun 2025 01:06:26 +0000 (03:06 +0200)
* grass touch protocol - Rebases to latest master to fix conflicts

* aight local tests are passing lets see if our golf works

* It is 5 am and our ass COMPLETELY overcomplicated this lmaooo

* Addresses feedback - Clarifies comments, swaps internal var names for grasstouchless and selfdestructive, makes the third tier a little less demanding, and fixes 1 hours

* Addresses review - conflict fix

* This too

* Axes playtime exclusion for ghosts

* Use switch expression

code style nit

* Refactor/cleanup

Use IGameTiming.RealTime to track time instead of DateTime. Use nullable instead of magic values.

Expose the current day value through a property that is always up to date, instead of making the API to read the CVar that updates at inconsistent times. This also makes it trivial to debug with VV.

Other minor cleanup like using string interp, code style fixes, comments, etc.

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
Content.Client/Entry/EntryPoint.cs
Content.Client/IoC/ClientContentIoC.cs
Content.Client/Lobby/LobbyState.cs
Content.Client/Lobby/UI/LobbyGui.xaml
Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs [new file with mode: 0644]
Content.Shared/CCVar/CCVars.Misc.cs
Resources/Locale/en-US/lobby/lobby-state.ftl

index 322b6e113caf7a0d9c3f0d4e66869539ee554787..a458e24aed6741dd28fc099507c0f8c7c2e136f4 100644 (file)
@@ -14,6 +14,7 @@ using Content.Client.Lobby;
 using Content.Client.MainMenu;
 using Content.Client.Parallax.Managers;
 using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Playtime;
 using Content.Client.Radiation.Overlays;
 using Content.Client.Replay;
 using Content.Client.Screenshot;
@@ -74,6 +75,7 @@ namespace Content.Client.Entry
         [Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
         [Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
         [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+        [Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!;
 
         public override void Init()
         {
@@ -136,6 +138,7 @@ namespace Content.Client.Entry
             _extendedDisconnectInformation.Initialize();
             _jobRequirements.Initialize();
             _playbackMan.Initialize();
+            _clientsidePlaytimeManager.Initialize();
 
             //AUTOSCALING default Setup!
             _configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
index 1ea7868e9a5721e93819f3ccad912e5a7aef482d..ed2199bb81f71a344db75a69594558ac025d2ca5 100644 (file)
@@ -12,6 +12,7 @@ using Content.Client.Launcher;
 using Content.Client.Mapping;
 using Content.Client.Parallax.Managers;
 using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Playtime;
 using Content.Client.Replay;
 using Content.Client.Screenshot;
 using Content.Client.Stylesheets;
@@ -60,6 +61,7 @@ namespace Content.Client.IoC
             collection.Register<PlayerRateLimitManager>();
             collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
             collection.Register<TitleWindowManager>();
+            collection.Register<ClientsidePlaytimeTrackingManager>();
         }
     }
 }
index 4677245509e496086eb179e6f36565dbc6b54e80..867a7bb8a5931f1965d2612b8c4dab6b8344bcda 100644 (file)
@@ -3,6 +3,7 @@ using Content.Client.GameTicking.Managers;
 using Content.Client.LateJoin;
 using Content.Client.Lobby.UI;
 using Content.Client.Message;
+using Content.Client.Playtime;
 using Content.Client.UserInterface.Systems.Chat;
 using Content.Client.Voting;
 using Content.Shared.CCVar;
@@ -26,6 +27,7 @@ namespace Content.Client.Lobby
         [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
         [Dependency] private readonly IGameTiming _gameTiming = default!;
         [Dependency] private readonly IVoteManager _voteManager = default!;
+        [Dependency] private readonly ClientsidePlaytimeTrackingManager _playtimeTracking = default!;
 
         private ClientGameTicker _gameTicker = default!;
         private ContentAudioSystem _contentAudioSystem = default!;
@@ -195,6 +197,26 @@ namespace Content.Client.Lobby
             {
                 Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
             }
+
+            var minutesToday = _playtimeTracking.PlaytimeMinutesToday;
+            if (minutesToday > 60)
+            {
+                Lobby!.PlaytimeComment.Visible = true;
+
+                var hoursToday = Math.Round(minutesToday / 60f, 1);
+
+                var chosenString = minutesToday switch
+                {
+                    < 180 => "lobby-state-playtime-comment-normal",
+                    < 360 => "lobby-state-playtime-comment-concerning",
+                    < 720 => "lobby-state-playtime-comment-grasstouchless",
+                    _ => "lobby-state-playtime-comment-selfdestructive"
+                };
+
+                Lobby.PlaytimeComment.SetMarkup(Loc.GetString(chosenString, ("hours", hoursToday)));
+            }
+            else
+                Lobby!.PlaytimeComment.Visible = false;
         }
 
         private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
index 761795452e2849dfa65b9945681312034031fa0f..64291816ab81c06a9c0764471f30d7fcfceb2e39 100644 (file)
@@ -41,6 +41,7 @@
                                                 StyleClasses="ButtonBig" MinWidth="137" />
                                     </BoxContainer>
                                 </controls:StripeBack>
+                                <RichTextLabel Name="PlaytimeComment" Visible="False" Access="Public" HorizontalAlignment="Center" />
                             </BoxContainer>
                         </PanelContainer>
                         <!-- Voting Popups -->
diff --git a/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs b/Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
new file mode 100644 (file)
index 0000000..330ff91
--- /dev/null
@@ -0,0 +1,108 @@
+using Content.Shared.CCVar;
+using Robust.Client.Player;
+using Robust.Shared.Network;
+using Robust.Shared.Configuration;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Playtime;
+
+/// <summary>
+///     Keeps track of how long the player has played today.
+/// </summary>
+/// <remarks>
+/// <para>
+///     Playtime is treated as any time in which the player is attached to an entity.
+///     This notably excludes scenarios like the lobby.
+/// </para>
+/// </remarks>
+public sealed class ClientsidePlaytimeTrackingManager
+{
+    [Dependency] private readonly IClientNetManager _clientNetManager = default!;
+    [Dependency] private readonly IConfigurationManager _configurationManager = default!;
+    [Dependency] private readonly ILogManager _logManager = default!;
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+    private ISawmill _sawmill = default!;
+
+    private const string InternalDateFormat = "yyyy-MM-dd";
+
+    [ViewVariables]
+    private TimeSpan? _mobAttachmentTime;
+
+    /// <summary>
+    /// The total amount of time played today, in minutes.
+    /// </summary>
+    [ViewVariables]
+    public float PlaytimeMinutesToday
+    {
+        get
+        {
+            var cvarValue = _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
+            if (_mobAttachmentTime == null)
+                return cvarValue;
+
+            return cvarValue + (float)(_gameTiming.RealTime - _mobAttachmentTime.Value).TotalMinutes;
+        }
+    }
+
+    public void Initialize()
+    {
+        _sawmill = _logManager.GetSawmill("clientplaytime");
+        _clientNetManager.Connected += OnConnected;
+
+        // The downside to relying on playerattached and playerdetached is that unsaved playtime won't be saved in the event of a crash
+        // But then again, the config doesn't get saved in the event of a crash, either, so /shrug
+        // Playerdetached gets called on quit, though, so at least that's covered.
+        _playerManager.LocalPlayerAttached += OnPlayerAttached;
+        _playerManager.LocalPlayerDetached += OnPlayerDetached;
+    }
+
+    private void OnConnected(object? sender, NetChannelArgs args)
+    {
+        var datatimey = DateTime.Now;
+        _sawmill.Info($"Current day: {datatimey.Day} Current Date: {datatimey.Date.ToString(InternalDateFormat)}");
+
+        var recordedDateString = _configurationManager.GetCVar(CCVars.PlaytimeLastConnectDate);
+        var formattedDate = datatimey.Date.ToString(InternalDateFormat);
+
+        if (formattedDate == recordedDateString)
+            return;
+
+        _configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, 0);
+        _configurationManager.SetCVar(CCVars.PlaytimeLastConnectDate, formattedDate);
+    }
+
+    private void OnPlayerAttached(EntityUid entity)
+    {
+        _mobAttachmentTime = _gameTiming.RealTime;
+    }
+
+    private void OnPlayerDetached(EntityUid entity)
+    {
+        if (_mobAttachmentTime == null)
+            return;
+
+        var newTimeValue = PlaytimeMinutesToday;
+
+        _mobAttachmentTime = null;
+
+        var timeDiffMinutes = newTimeValue - _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
+        if (timeDiffMinutes < 0)
+        {
+            _sawmill.Error("Time differential on player detachment somehow less than zero!");
+            return;
+        }
+
+        // At less than 1 minute of time diff, there's not much point, and saving regardless will brick tests
+        // The reason this isn't checking for 0 is because TotalMinutes is fractional, rather than solely whole minutes
+        if (timeDiffMinutes < 1)
+            return;
+
+        _configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, newTimeValue);
+
+        _sawmill.Info($"Recorded {timeDiffMinutes} minutes of living playtime!");
+
+        _configurationManager.SaveToFile(); // We don't like that we have to save the entire config just to store playtime stats '^'
+    }
+}
index 5fda4dc2fdb6a4210d5d8405e2be59e386619584..3e8a7badf9e91261c39e949f89457cc09e7a53e5 100644 (file)
@@ -94,4 +94,19 @@ public sealed partial class CCVars
     /// </summary>
     public static readonly CVarDef<float> PointingCooldownSeconds =
         CVarDef.Create("pointing.cooldown_seconds", 0.5f, CVar.SERVERONLY);
+
+    /// <summary>
+    ///     The last time the client recorded a valid connection to a game server.
+    ///     Used in conjunction with <see cref="PlaytimeMinutesToday"/> to track how long the player has been playing for the given day.
+    /// </summary>
+    public static readonly CVarDef<string> PlaytimeLastConnectDate =
+        CVarDef.Create("playtime.last_connect_date", "", CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    ///     The total minutes that the client has spent since the date of last connection.
+    ///     This is reset to 0 when the last connect date is updated.
+    ///     Do not read this value directly, use <code>ClientsidePlaytimeTrackingManager</code> instead.
+    /// </summary>
+    public static readonly CVarDef<float> PlaytimeMinutesToday =
+        CVarDef.Create("playtime.minutes_today", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
 }
index 0c4c401daa37def27619a432ce9cdb3e9a1ff04e..0c92923b1c9927d4e733554bf0e1749559357c4a 100644 (file)
@@ -21,3 +21,11 @@ lobby-state-song-text = Playing: [color=white]{$songTitle}[/color] by [color=whi
 lobby-state-song-no-song-text = No lobby song playing.
 lobby-state-song-unknown-title = [color=dimgray]Unknown title[/color]
 lobby-state-song-unknown-artist = [color=dimgray]Unknown artist[/color]
+lobby-state-playtime-comment-normal =
+    You've spent {$hours} {$hours ->
+    [1]hour
+    *[other]hours
+    } ingame today. Remember to take breaks!
+lobby-state-playtime-comment-concerning = You've played for {$hours} hours today. Please take a break.
+lobby-state-playtime-comment-grasstouchless = {$hours} hours. Consider logging off to attend to your needs.
+lobby-state-playtime-comment-selfdestructive = {$hours} hours. Really?