using System.Diagnostics.CodeAnalysis; using Content.Client.Players.PlayTimeTracking; using Content.Shared.BugReport; using Content.Shared.CCVar; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Client.UserInterface.Systems.BugReport.Windows; [GenerateTypedNameReferences] public sealed partial class BugReportWindow : DefaultWindow { [Dependency] private readonly IConfigurationManager _cfg = default!; // TODO: Use SharedPlaytimeManager when its refactored out of job requirements [Dependency] private readonly JobRequirementsManager _job = default!; // This action gets invoked when the user submits a bug report. public event Action? OnBugReportSubmitted; private DateTime _lastIsEnabledUpdated; private readonly TimeSpan _isEnabledUpdateInterval = TimeSpan.FromSeconds(1); // These are NOT always up to date. If someone disconnects and reconnects, the values will be reset. // The only other way of getting updated values would be a message from client -> server then from server -> client. // I don't think that is worth the added complexity. private DateTime _lastBugReportSubmittedTime = DateTime.MinValue; private int _amountOfBugReportsSubmitted; private readonly ConfigurationMultiSubscriptionBuilder _configSub; #region ccvar private bool _enablePlayerBugReports; private int _minimumPlaytimeBugReports; private int _minimumTimeBetweenBugReports; private int _maximumBugReportsPerRound; private int _maximumBugReportTitleLength; private int _minimumBugReportTitleLength; private int _maximumBugReportDescriptionLength; private int _minimumBugReportDescriptionLength; #endregion public BugReportWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _configSub = _cfg.SubscribeMultiple() .OnValueChanged(CCVars.EnablePlayerBugReports, x => _enablePlayerBugReports = x, true) .OnValueChanged(CCVars.MinimumPlaytimeInMinutesToEnableBugReports, x => _minimumPlaytimeBugReports = x, true) .OnValueChanged(CCVars.MinimumSecondsBetweenBugReports, x => _minimumTimeBetweenBugReports = x, true) .OnValueChanged(CCVars.MaximumBugReportsPerRound, x => _maximumBugReportsPerRound = x, true) .OnValueChanged(CCVars.MaximumBugReportTitleLength, x => _maximumBugReportTitleLength = x, true) .OnValueChanged(CCVars.MinimumBugReportTitleLength, x => _minimumBugReportTitleLength = x, true) .OnValueChanged(CCVars.MaximumBugReportDescriptionLength, x => _maximumBugReportDescriptionLength = x, true) .OnValueChanged(CCVars.MinimumBugReportDescriptionLength, x => _minimumBugReportDescriptionLength = x, true); // Hook up the events SubmitButton.OnPressed += _ => OnSubmitButtonPressed(); BugReportTitle.OnTextChanged += _ => HandleInputChange(); BugReportDescription.OnTextChanged += _ => HandleInputChange(); OnOpen += UpdateEnabled; HandleInputChange(); UpdateEnabled(); } private void OnSubmitButtonPressed() { var report = new PlayerBugReportInformation { BugReportTitle = BugReportTitle.Text, BugReportDescription = Rope.Collapse(BugReportDescription.TextRope), }; OnBugReportSubmitted?.Invoke(report); _lastBugReportSubmittedTime = DateTime.UtcNow; _amountOfBugReportsSubmitted++; BugReportTitle.Text = string.Empty; BugReportDescription.TextRope = Rope.Leaf.Empty; HandleInputChange(); UpdateEnabled(); } /// /// Deals with the user changing their input. Ensures that things that depend on what the user has inputted get updated /// (E.g. the amount of characters they have typed) /// private void HandleInputChange() { var titleLen = BugReportTitle.Text.Length; var descriptionLen = BugReportDescription.TextLength; var invalidTitleLen = titleLen < _minimumBugReportTitleLength || titleLen > _maximumBugReportTitleLength; var invalidDescriptionLen = descriptionLen < _minimumBugReportDescriptionLength || descriptionLen > _maximumBugReportDescriptionLength; TitleCharacterCounter.Text = Loc.GetString("bug-report-window-submit-char-split", ("typed", titleLen), ("total", _maximumBugReportTitleLength)); TitleCharacterCounter.FontColorOverride = invalidTitleLen ? Color.Red : Color.Green; DescriptionCharacterCounter.Text = Loc.GetString("bug-report-window-submit-char-split", ("typed", descriptionLen), ("total", _maximumBugReportDescriptionLength)); DescriptionCharacterCounter.FontColorOverride = invalidDescriptionLen ? Color.Red : Color.Green; SubmitButton.Disabled = invalidTitleLen || invalidDescriptionLen; PlaceholderCenter.Visible = descriptionLen == 0; } /// /// Checks if the bug report window should be enabled for this client. /// private bool IsEnabled([NotNullWhen(false)] out string? errorMessage) { errorMessage = null; if (!_enablePlayerBugReports) { errorMessage = Loc.GetString("bug-report-window-disabled-not-enabled"); return false; } if (TimeSpan.FromMinutes(_minimumPlaytimeBugReports) > _job.FetchOverallPlaytime()) { errorMessage = Loc.GetString("bug-report-window-disabled-playtime"); return false; } if (_amountOfBugReportsSubmitted >= _maximumBugReportsPerRound) { errorMessage = Loc.GetString("bug-report-window-disabled-submissions", ("num", _maximumBugReportsPerRound)); return false; } var timeSinceLastReport = DateTime.UtcNow - _lastBugReportSubmittedTime; var timeBetweenBugReports = TimeSpan.FromSeconds(_minimumTimeBetweenBugReports); if (timeSinceLastReport <= timeBetweenBugReports) { var time = timeBetweenBugReports - timeSinceLastReport; errorMessage = Loc.GetString("bug-report-window-disabled-cooldown", ("time", time.ToString(@"d\.hh\:mm\:ss"))); return false; } return true; } // Update the state of the window to display either the bug report window or an error explaining why you can't submit a report. private void UpdateEnabled() { var isEnabled = IsEnabled(out var errorMessage); DisabledLabel.Text = errorMessage; DisabledLabel.Visible = !isEnabled; BugReportContainer.Visible = isEnabled; _lastIsEnabledUpdated = DateTime.UtcNow; } protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); if (!Visible) // Don't bother updating if no one can see the window anyway. return; if(DateTime.UtcNow - _lastIsEnabledUpdated > _isEnabledUpdateInterval) UpdateEnabled(); } public void CleanupCCvars() { _configSub.Dispose(); } }