From 4fbebb6917e3dc906fb492f3b78a2b4d669e1b9b Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:54:53 +0100 Subject: [PATCH] Roundstart antag role restrictions revival (#20108) Co-authored-by: Ray Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../JobRequirementsManager.cs | 18 +- .../UI/HumanoidProfileEditor.xaml.cs | 207 +++++++++--------- .../Controls/Roles/GhostRoleEntryButtons.xaml | 6 +- .../Controls/Roles/GhostRoleRulesWindow.xaml | 4 +- .../Controls/Roles/GhostRolesEntry.xaml.cs | 29 ++- .../Ghost/Controls/Roles/GhostRolesEui.cs | 19 +- .../Controls/Roles/GhostRolesWindow.xaml | 3 +- .../Controls/Roles/GhostRolesWindow.xaml.cs | 6 +- .../Roles/Components/GhostRoleComponent.cs | 4 + Content.Server/Ghost/Roles/GhostRoleSystem.cs | 2 +- .../Ghost/Roles/GhostRolesEuiMessages.cs | 2 + Content.Shared/Roles/AntagPrototype.cs | 71 +++--- Content.Shared/Roles/JobRequirements.cs | 6 +- .../ui/humanoid-profile-editor.ftl | 2 + 14 files changed, 229 insertions(+), 150 deletions(-) diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 9d5801b925..c67c759dfd 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -97,21 +97,29 @@ public sealed class JobRequirementsManager } var player = _playerManager.LocalPlayer?.Session; - if (player == null) return true; - var reasonBuilder = new StringBuilder(); + return CheckRoleTime(job.Requirements, out reason); + } + + public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = null; + + if (requirements == null) + return true; - foreach (var requirement in job.Requirements) + var reasons = new List(); + foreach (var requirement in requirements) { if (JobRequirements.TryRequirementMet(requirement, _roles, out var jobReason, _entManager, _prototypes)) continue; - reasonBuilder.AppendLine(jobReason.ToMarkup()); + reasons.Add(jobReason.ToMarkup()); } - reason = reasonBuilder.Length == 0 ? null : FormattedMessage.FromMarkup(reasonBuilder.ToString().Trim()); + reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons)); return reason == null; } } diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs index 59e08d344f..c9a64eb097 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs @@ -393,13 +393,16 @@ namespace Content.Client.Preferences.UI foreach (var antag in prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name))) { if (!antag.SetPreference) - { continue; - } var selector = new AntagPreferenceSelector(antag); _antagList.AddChild(selector); _antagPreferences.Add(selector); + if (selector.Disabled) + { + Profile = Profile?.WithAntagPreference(antag.ID, false); + IsDirty = true; + } selector.PreferenceChanged += preference => { @@ -587,19 +590,15 @@ namespace Content.Client.Preferences.UI foreach (var jobSelector in _jobPriorities) { // Sync other selectors with the same job in case of multiple department jobs - if (jobSelector.Job == selector.Job) + if (jobSelector.Proto == selector.Proto) { jobSelector.Priority = priority; } - - // Lower any other high priorities to medium. - if (priority == JobPriority.High) + else if (priority == JobPriority.High && jobSelector.Priority == JobPriority.High) { - if (jobSelector.Job != selector.Job && jobSelector.Priority == JobPriority.High) - { - jobSelector.Priority = JobPriority.Medium; - Profile = Profile?.WithJobPriority(jobSelector.Job.ID, JobPriority.Medium); - } + // Lower any other high priorities to medium. + jobSelector.Priority = JobPriority.Medium; + Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium); } } }; @@ -1131,7 +1130,7 @@ namespace Content.Client.Preferences.UI { foreach (var prioritySelector in _jobPriorities) { - var jobId = prioritySelector.Job.ID; + var jobId = prioritySelector.Proto.ID; var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never; @@ -1139,55 +1138,29 @@ namespace Content.Client.Preferences.UI } } - private sealed class JobPrioritySelector : Control + private abstract class RequirementsSelector : Control { - public JobPrototype Job { get; } - private readonly RadioOptions _optionButton; - - public JobPriority Priority - { - get => (JobPriority) _optionButton.SelectedValue; - set => _optionButton.SelectByValue((int) value); - } - - public event Action? PriorityChanged; + public T Proto { get; } + public bool Disabled => _lockStripe.Visible; + protected readonly RadioOptions Options; private StripeBack _lockStripe; private Label _requirementsLabel; - private Label _jobTitle; - public JobPrioritySelector(JobPrototype job, IPrototypeManager prototypeManager) + protected RequirementsSelector(T proto) { - Job = job; + Proto = proto; - _optionButton = new RadioOptions(RadioOptionsLayout.Horizontal) + Options = new RadioOptions(RadioOptionsLayout.Horizontal) { FirstButtonStyle = StyleBase.ButtonOpenRight, ButtonStyle = StyleBase.ButtonOpenBoth, LastButtonStyle = StyleBase.ButtonOpenLeft }; //Override default radio option button width - _optionButton.GenerateItem = GenerateButton; - // Text, Value - _optionButton.AddItem(Loc.GetString("humanoid-profile-editor-job-priority-high-button"), (int) JobPriority.High); - _optionButton.AddItem(Loc.GetString("humanoid-profile-editor-job-priority-medium-button"), (int) JobPriority.Medium); - _optionButton.AddItem(Loc.GetString("humanoid-profile-editor-job-priority-low-button"), (int) JobPriority.Low); - _optionButton.AddItem(Loc.GetString("humanoid-profile-editor-job-priority-never-button"), (int) JobPriority.Never); - - _optionButton.OnItemSelected += args => - { - _optionButton.Select(args.Id); - PriorityChanged?.Invoke(Priority); - }; - - var icon = new TextureRect - { - TextureScale = new Vector2(2, 2), - Stretch = TextureRect.StretchMode.KeepCentered - }; + Options.GenerateItem = GenerateButton; - var jobIcon = prototypeManager.Index(job.Icon); - icon.Texture = jobIcon.Icon.Frame0(); + Options.OnItemSelected += args => Options.Select(args.Id); _requirementsLabel = new Label() { @@ -1208,30 +1181,40 @@ namespace Content.Client.Preferences.UI } }; - _jobTitle = new Label() - { - Margin = new Thickness(5f,0,5f,0), - Text = job.LocalizedName, - MinSize = new Vector2(200, 0), - MouseFilter = MouseFilterMode.Stop - }; + // Setup must be called after + } - if (job.LocalizedDescription != null) + /// + /// Actually adds the controls, must be called in the inheriting class' constructor. + /// + protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null) + { + foreach (var (text, value) in items) { - _jobTitle.ToolTip = job.LocalizedDescription; + Options.AddItem(Loc.GetString(text), value); } - AddChild(new BoxContainer + var titleLabel = new Label() + { + Margin = new Thickness(5f, 0, 5f, 0), + Text = title, + MinSize = new Vector2(titleSize, 0), + MouseFilter = MouseFilterMode.Stop, + ToolTip = description + }; + + var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal, - Children = - { - icon, - _jobTitle, - _optionButton, - _lockStripe, - } - }); + }; + + if (icon != null) + container.AddChild(icon); + container.AddChild(titleLabel); + container.AddChild(Options); + container.AddChild(_lockStripe); + + AddChild(container); } public void LockRequirements(FormattedMessage requirements) @@ -1240,25 +1223,58 @@ namespace Content.Client.Preferences.UI tooltip.SetMessage(requirements); _lockStripe.TooltipSupplier = _ => tooltip; _lockStripe.Visible = true; - _optionButton.Visible = false; + Options.Visible = false; } // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn public void UnlockRequirements() { - _requirementsLabel.Visible = false; _lockStripe.Visible = false; - _optionButton.Visible = true; + Options.Visible = true; } private Button GenerateButton(string text, int value) { - var btn = new Button + return new Button { Text = text, MinWidth = 90 }; - return btn; + } + } + + private sealed class JobPrioritySelector : RequirementsSelector + { + public JobPriority Priority + { + get => (JobPriority) Options.SelectedValue; + set => Options.SelectByValue((int) value); + } + + public event Action? PriorityChanged; + + public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan) + : base(proto) + { + Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority); + + var items = new[] + { + ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High), + ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium), + ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low), + ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never), + }; + + var icon = new TextureRect + { + TextureScale = new Vector2(2, 2), + Stretch = TextureRect.StretchMode.KeepCentered + }; + var jobIcon = protoMan.Index(proto.Icon); + icon.Texture = jobIcon.Icon.Frame0(); + + Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon); } } @@ -1266,9 +1282,8 @@ namespace Content.Client.Preferences.UI { foreach (var preferenceSelector in _antagPreferences) { - var antagId = preferenceSelector.Antag.ID; + var antagId = preferenceSelector.Proto.ID; var preference = Profile?.AntagPreferences.Contains(antagId) ?? false; - preferenceSelector.Preference = preference; } } @@ -1284,44 +1299,38 @@ namespace Content.Client.Preferences.UI } } - private sealed class AntagPreferenceSelector : Control + private sealed class AntagPreferenceSelector : RequirementsSelector { - public AntagPrototype Antag { get; } - private readonly CheckBox _checkBox; - + // 0 is yes and 1 is no public bool Preference { - get => _checkBox.Pressed; - set => _checkBox.Pressed = value; + get => Options.SelectedValue == 0; + set => Options.Select((value && !Disabled) ? 0 : 1); } public event Action? PreferenceChanged; - public AntagPreferenceSelector(AntagPrototype antag) + public AntagPreferenceSelector(AntagPrototype proto) + : base(proto) { - Antag = antag; - - _checkBox = new CheckBox {Text = Loc.GetString(antag.Name)}; - _checkBox.OnToggled += OnCheckBoxToggled; + Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference); - if (antag.Description != null) + var items = new[] { - _checkBox.ToolTip = Loc.GetString(antag.Description); - } - - AddChild(new BoxContainer + ("humanoid-profile-editor-antag-preference-yes-button", 0), + ("humanoid-profile-editor-antag-preference-no-button", 1) + }; + var title = Loc.GetString(proto.Name); + var description = Loc.GetString(proto.Objective); + Setup(items, title, 250, description); + + // immediately lock requirements if they arent met. + // another function checks Disabled after creating the selector so this has to be done now + var requirements = IoCManager.Resolve(); + if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason)) { - Orientation = LayoutOrientation.Horizontal, - Children = - { - _checkBox - } - }); - } - - private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) - { - PreferenceChanged?.Invoke(Preference); + LockRequirements(reason); + } } } diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml index b76cd8821f..92e38e35e0 100644 --- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml +++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRoleEntryButtons.xaml @@ -4,10 +4,12 @@ Access="Public" Text="{Loc 'ghost-roles-window-request-role-button'}" StyleClasses="OpenRight" - HorizontalAlignment="Left"/> + HorizontalAlignment="Left" + SetWidth="150"/>