From 7fd2541aee6c2a9a88305ecfcf2be6535f5da0f2 Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Wed, 28 Aug 2024 00:33:30 +0000
Subject: [PATCH] job whitelists panel !!! (#1678)
* make JobPrototype.Whitelisted respect blanket whitelist, make jobs use it
* add jobwhitelists command and ui
* add job whitelist button to player panel
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
---
.../UI/PlayerPanel/PlayerPanel.xaml | 1 +
.../UI/PlayerPanel/PlayerPanel.xaml.cs | 4 +
.../UI/PlayerPanel/PlayerPanelEui.cs | 1 +
.../UI/DepartmentWhitelistPanel.xaml | 11 +++
.../UI/DepartmentWhitelistPanel.xaml.cs | 46 ++++++++++
.../Administration/UI/JobWhitelistsEui.cs | 40 +++++++++
.../UI/JobWhitelistsWindow.xaml | 11 +++
.../UI/JobWhitelistsWindow.xaml.cs | 46 ++++++++++
.../Commands/JobWhitelistsCommand.cs | 45 ++++++++++
.../DeltaV/Administration/JobWhitelistsEui.cs | 90 +++++++++++++++++++
.../JobWhitelist/JobWhitelistManager.cs | 5 ++
.../Administration/JobWhitelistsEuiState.cs | 35 ++++++++
.../JobRequirement/WhitelistRequirement.cs | 3 +
.../deltav/administration/ui/player-panel.ftl | 1 +
.../Locale/en-US/deltav/info/whitelists.ftl | 3 +
.../Roles/Jobs/Justice/chief_justice.yml | 2 +-
.../Prototypes/Roles/Jobs/Command/captain.yml | 2 +-
.../Roles/Jobs/Security/head_of_security.yml | 2 +-
18 files changed, 345 insertions(+), 3 deletions(-)
create mode 100644 Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml
create mode 100644 Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml.cs
create mode 100644 Content.Client/DeltaV/Administration/UI/JobWhitelistsEui.cs
create mode 100644 Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml
create mode 100644 Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml.cs
create mode 100644 Content.Server/DeltaV/Administration/Commands/JobWhitelistsCommand.cs
create mode 100644 Content.Server/DeltaV/Administration/JobWhitelistsEui.cs
create mode 100644 Content.Shared/DeltaV/Administration/JobWhitelistsEuiState.cs
create mode 100644 Resources/Locale/en-US/deltav/administration/ui/player-panel.ftl
create mode 100644 Resources/Locale/en-US/deltav/info/whitelists.ftl
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml
index 8feec273b4..5b3b9f007c 100644
--- a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml
@@ -31,6 +31,7 @@
+
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
index 53cc8faa10..2b69259db7 100644
--- a/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanel.xaml.cs
@@ -26,6 +26,7 @@ public sealed partial class PlayerPanel : FancyWindow
public event Action? OnLogs;
public event Action? OnDelete;
public event Action? OnRejuvenate;
+ public event Action? OnOpenJobWhitelists; // DeltaV
public NetUserId? TargetPlayer;
public string? TargetUsername;
@@ -52,6 +53,8 @@ public sealed partial class PlayerPanel : FancyWindow
LogsButton.OnPressed += _ => OnLogs?.Invoke();
DeleteButton.OnPressed += _ => OnDelete?.Invoke();
RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
+
+ JobWhitelistsButton.OnPressed += _ => OnOpenJobWhitelists?.Invoke(TargetPlayer); // DeltaV: Job whitelists
}
public void SetUsername(string player)
@@ -128,5 +131,6 @@ public sealed partial class PlayerPanel : FancyWindow
LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
+ JobWhitelistsButton.Disabled = !_adminManager.HasFlag(AdminFlags.Whitelist); // DeltaV
}
}
diff --git a/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs b/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs
index 87ce756046..1ec912cf91 100644
--- a/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs
+++ b/Content.Client/Administration/UI/PlayerPanel/PlayerPanelEui.cs
@@ -38,6 +38,7 @@ public sealed class PlayerPanelEui : BaseEui
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
+ PlayerPanel.OnOpenJobWhitelists += id => _console.ExecuteCommand($"jobwhitelists \"{id}\""); // DeltaV
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
}
diff --git a/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml b/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml
new file mode 100644
index 0000000000..58251cf9a3
--- /dev/null
+++ b/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml.cs b/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml.cs
new file mode 100644
index 0000000000..29d2672bd0
--- /dev/null
+++ b/Content.Client/DeltaV/Administration/UI/DepartmentWhitelistPanel.xaml.cs
@@ -0,0 +1,46 @@
+using Content.Shared.Roles;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.DeltaV.Administration.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class DepartmentWhitelistPanel : PanelContainer
+{
+ public Action, bool>? OnSetJob;
+
+ public DepartmentWhitelistPanel(DepartmentPrototype department, IPrototypeManager proto, HashSet> whitelists)
+ {
+ RobustXamlLoader.Load(this);
+
+ var allWhitelisted = true;
+ foreach (var id in department.Roles)
+ {
+ var thisJob = id; // closure capturing funny
+ var button = new CheckBox();
+ button.Text = proto.Index(id).LocalizedName;
+ button.Pressed = whitelists.Contains(id);
+ button.OnPressed += _ => OnSetJob?.Invoke(thisJob, button.Pressed);
+ JobsContainer.AddChild(button);
+
+ allWhitelisted &= button.Pressed;
+ }
+
+ Department.Text = Loc.GetString(department.Name);
+ Department.Modulate = department.Color;
+ Department.Pressed = allWhitelisted;
+ Department.OnPressed += args =>
+ {
+ foreach (var id in department.Roles)
+ {
+ // only request to whitelist roles that aren't already whitelisted, and vice versa
+ if (whitelists.Contains(id) != Department.Pressed)
+ OnSetJob?.Invoke(id, Department.Pressed);
+ }
+ };
+ }
+}
diff --git a/Content.Client/DeltaV/Administration/UI/JobWhitelistsEui.cs b/Content.Client/DeltaV/Administration/UI/JobWhitelistsEui.cs
new file mode 100644
index 0000000000..c746f42f8f
--- /dev/null
+++ b/Content.Client/DeltaV/Administration/UI/JobWhitelistsEui.cs
@@ -0,0 +1,40 @@
+using Content.Client.Eui;
+using Content.Shared.DeltaV.Administration;
+using Content.Shared.Eui;
+
+namespace Content.Client.DeltaV.Administration.UI;
+
+public sealed class JobWhitelistsEui : BaseEui
+{
+ private JobWhitelistsWindow Window;
+
+ public JobWhitelistsEui()
+ {
+ Window = new JobWhitelistsWindow();
+ Window.OnClose += () => SendMessage(new CloseEuiMessage());
+ Window.OnSetJob += (id, whitelisted) => SendMessage(new SetJobWhitelistedMessage(id, whitelisted));
+ }
+
+ public override void HandleState(EuiStateBase state)
+ {
+ if (state is not JobWhitelistsEuiState cast)
+ return;
+
+ Window.HandleState(cast);
+ }
+
+ public override void Opened()
+ {
+ base.Opened();
+
+ Window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ base.Closed();
+
+ Window.Close();
+ Window.Dispose();
+ }
+}
diff --git a/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml b/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml
new file mode 100644
index 0000000000..165f5ac3d7
--- /dev/null
+++ b/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml.cs b/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml.cs
new file mode 100644
index 0000000000..f01b67d6da
--- /dev/null
+++ b/Content.Client/DeltaV/Administration/UI/JobWhitelistsWindow.xaml.cs
@@ -0,0 +1,46 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Database;
+using Content.Shared.DeltaV.Administration;
+using Content.Shared.Roles;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.DeltaV.Administration.UI;
+
+///
+/// An admin panel to toggle whitelists for individual jobs or entire departments.
+/// This should generally be preferred to a blanket whitelist (Whitelisted: True) since
+/// being good with a batong doesn't mean you know engineering and vice versa.
+///
+[GenerateTypedNameReferences]
+public sealed partial class JobWhitelistsWindow : FancyWindow
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ public Action, bool>? OnSetJob;
+
+ public JobWhitelistsWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ PlayerName.Text = "???";
+ }
+
+ public void HandleState(JobWhitelistsEuiState state)
+ {
+ PlayerName.Text = state.PlayerName;
+
+ Departments.RemoveAllChildren();
+ foreach (var proto in _proto.EnumeratePrototypes())
+ {
+ var panel = new DepartmentWhitelistPanel(proto, _proto, state.Whitelists);
+ panel.OnSetJob += (id, whitelisting) => OnSetJob?.Invoke(id, whitelisting);
+ Departments.AddChild(panel);
+ }
+ }
+}
diff --git a/Content.Server/DeltaV/Administration/Commands/JobWhitelistsCommand.cs b/Content.Server/DeltaV/Administration/Commands/JobWhitelistsCommand.cs
new file mode 100644
index 0000000000..7e7efce2e1
--- /dev/null
+++ b/Content.Server/DeltaV/Administration/Commands/JobWhitelistsCommand.cs
@@ -0,0 +1,45 @@
+using Content.Server.Administration;
+using Content.Server.EUI;
+using Content.Shared.Administration;
+using Robust.Shared.Console;
+
+namespace Content.Server.DeltaV.Administration.Commands;
+
+///
+/// Opens the job whitelists panel for editing player whitelists.
+/// To use this ingame it's easiest to first open the player panel, then hit Job Whitelists.
+///
+[AdminCommand(AdminFlags.Whitelist)]
+public sealed class JobWhitelistsCommand : LocalizedCommands
+{
+ [Dependency] private readonly EuiManager _eui = default!;
+ [Dependency] private readonly IPlayerLocator _locator = default!;
+
+ public override string Command => "jobwhitelists";
+
+ public override async void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (shell.Player is not {} player)
+ {
+ shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server"));
+ return;
+ }
+
+ if (args.Length != 1)
+ {
+ shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments"));
+ shell.WriteLine(Help);
+ }
+
+ var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
+ if (located is null)
+ {
+ shell.WriteError(Loc.GetString("cmd-jobwhitelists-player-err"));
+ return;
+ }
+
+ var ui = new JobWhitelistsEui(located.UserId, located.Username);
+ ui.LoadWhitelists();
+ _eui.OpenEui(ui, player);
+ }
+}
diff --git a/Content.Server/DeltaV/Administration/JobWhitelistsEui.cs b/Content.Server/DeltaV/Administration/JobWhitelistsEui.cs
new file mode 100644
index 0000000000..dd435c3318
--- /dev/null
+++ b/Content.Server/DeltaV/Administration/JobWhitelistsEui.cs
@@ -0,0 +1,90 @@
+using System.Threading.Tasks;
+using Content.Server.Administration.Managers;
+using Content.Server.Database;
+using Content.Server.EUI;
+using Content.Server.Players.JobWhitelist;
+using Content.Shared.Administration;
+using Content.Shared.DeltaV.Administration;
+using Content.Shared.Eui;
+using Content.Shared.Roles;
+using Robust.Shared.Log;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.DeltaV.Administration;
+
+public sealed class JobWhitelistsEui : BaseEui
+{
+ [Dependency] private readonly IAdminManager _admin = default!;
+ [Dependency] private readonly ILogManager _log = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IServerDbManager _db = default!;
+ [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!;
+
+ private readonly ISawmill _sawmill;
+
+ public NetUserId PlayerId;
+ public string PlayerName;
+
+ public HashSet> Whitelists = new();
+
+ public JobWhitelistsEui(NetUserId playerId, string playerName)
+ {
+ IoCManager.InjectDependencies(this);
+
+ _sawmill = _log.GetSawmill("admin.job_whitelists_eui");
+
+ PlayerId = playerId;
+ PlayerName = playerName;
+ }
+
+ public async void LoadWhitelists()
+ {
+ var jobs = await _db.GetJobWhitelists(PlayerId.UserId);
+ foreach (var id in jobs)
+ {
+ if (_proto.HasIndex(id))
+ Whitelists.Add(id);
+ }
+
+ StateDirty();
+ }
+
+ public override EuiStateBase GetNewState()
+ {
+ return new JobWhitelistsEuiState(PlayerName, Whitelists);
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ if (msg is not SetJobWhitelistedMessage args)
+ return;
+
+ if (!_admin.HasAdminFlag(Player, AdminFlags.Whitelist))
+ {
+ _sawmill.Warning($"{Player.Name} ({Player.UserId}) tried to change role whitelists for {PlayerName} without whitelists flag");
+ return;
+ }
+
+ if (!_proto.HasIndex(args.Job))
+ return;
+
+ if (args.Whitelisting)
+ {
+ _jobWhitelist.AddWhitelist(PlayerId, args.Job);
+ Whitelists.Add(args.Job);
+ }
+ else
+ {
+ _jobWhitelist.RemoveWhitelist(PlayerId, args.Job);
+ Whitelists.Remove(args.Job);
+ }
+
+ var verb = args.Whitelisting ? "added" : "removed";
+ _sawmill.Info($"{Player.Name} ({Player.UserId}) {verb} whitelist for {args.Job} to player {PlayerName} ({PlayerId.UserId})");
+
+ StateDirty();
+ }
+}
diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs
index 04289a4098..89a7597015 100644
--- a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs
+++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.CCVar;
+using Content.Shared.Players; // DeltaV
using Content.Shared.Players.JobWhitelist;
using Content.Shared.Roles;
using Robust.Server.Player;
@@ -69,6 +70,10 @@ public sealed class JobWhitelistManager : IPostInjectInit
return true;
}
+ // DeltaV: Blanket player whitelist allows all roles
+ if (session.ContentData()?.Whitelisted ?? false)
+ return true;
+
return IsWhitelisted(session.UserId, job);
}
diff --git a/Content.Shared/DeltaV/Administration/JobWhitelistsEuiState.cs b/Content.Shared/DeltaV/Administration/JobWhitelistsEuiState.cs
new file mode 100644
index 0000000000..a5e64739e1
--- /dev/null
+++ b/Content.Shared/DeltaV/Administration/JobWhitelistsEuiState.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Eui;
+using Content.Shared.Roles;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DeltaV.Administration;
+
+[Serializable, NetSerializable]
+public sealed class JobWhitelistsEuiState : EuiStateBase
+{
+ public string PlayerName;
+ public HashSet> Whitelists;
+
+ public JobWhitelistsEuiState(string playerName, HashSet> whitelists)
+ {
+ PlayerName = playerName;
+ Whitelists = whitelists;
+ }
+}
+
+///
+/// Tries to add or remove a whitelist of a job for a player.
+///
+[Serializable, NetSerializable]
+public sealed class SetJobWhitelistedMessage : EuiMessageBase
+{
+ public ProtoId Job;
+ public bool Whitelisting;
+
+ public SetJobWhitelistedMessage(ProtoId job, bool whitelisting)
+ {
+ Job = job;
+ Whitelisting = whitelisting;
+ }
+}
diff --git a/Content.Shared/DeltaV/Roles/JobRequirement/WhitelistRequirement.cs b/Content.Shared/DeltaV/Roles/JobRequirement/WhitelistRequirement.cs
index dfebb583d1..2cdd2d14b3 100644
--- a/Content.Shared/DeltaV/Roles/JobRequirement/WhitelistRequirement.cs
+++ b/Content.Shared/DeltaV/Roles/JobRequirement/WhitelistRequirement.cs
@@ -9,6 +9,9 @@ namespace Content.Shared.Roles;
///
/// Requires the player be globally whitelisted to play a role.
///
+///
+/// Don't use this for jobs, use whitelisted: true on the JobPrototype instead.
+///
[Serializable, NetSerializable]
public sealed partial class WhitelistRequirement : JobRequirement
{
diff --git a/Resources/Locale/en-US/deltav/administration/ui/player-panel.ftl b/Resources/Locale/en-US/deltav/administration/ui/player-panel.ftl
new file mode 100644
index 0000000000..208268d589
--- /dev/null
+++ b/Resources/Locale/en-US/deltav/administration/ui/player-panel.ftl
@@ -0,0 +1 @@
+player-panel-job-whitelists = Job Whitelists
diff --git a/Resources/Locale/en-US/deltav/info/whitelists.ftl b/Resources/Locale/en-US/deltav/info/whitelists.ftl
new file mode 100644
index 0000000000..b67fc3e0ad
--- /dev/null
+++ b/Resources/Locale/en-US/deltav/info/whitelists.ftl
@@ -0,0 +1,3 @@
+cmd-jobwhitelists-desc = Opens the job whitelists window
+cmd-jobwhitelists-help = Usage: jobwhitelists [name or user guid]
+cmd-jobwhitelists-player-err = The specified player could not be found
diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml
index aab0e4e704..ef14fc0f4d 100644
--- a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml
+++ b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml
@@ -15,13 +15,13 @@
time: 36000 # 10 hours
- !type:OverallPlaytimeRequirement
time: 90000 # 25 hours
- - !type:WhitelistRequirement # whitelist requirement because I don't want any dingus judges
weight: 20
startingGear: CJGear
icon: "JobIconChiefJustice"
requireAdminNotify: true
supervisors: job-supervisors-captain
canBeAntag: false
+ whitelisted: true # DeltaV: whitelist requirement because I don't want any dingus judges
access:
- Command
- ChiefJustice
diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml
index 4d43865769..3e64124b25 100644
--- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml
+++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml
@@ -24,7 +24,6 @@
time: 108000 # DeltaV - 30 hours, was 15
- !type:OverallPlaytimeRequirement # DeltaV - Playtime requirement
time: 108000 # 30 hours
- - !type:WhitelistRequirement # DeltaV - Whitelist requirement
- !type:AgeRequirement
requiredAge: 21
weight: 20
@@ -34,6 +33,7 @@
joinNotifyCrew: true
supervisors: job-supervisors-centcom
canBeAntag: false
+ whitelisted: true # DeltaV
accessGroups:
- AllAccess
special:
diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
index f25ea56e92..f3dc8872ba 100644
--- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
+++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
@@ -15,7 +15,6 @@
time: 36000 # 10 hours
- !type:OverallPlaytimeRequirement
time: 90000 # DeltaV - 25 hours, was 40
- - !type:WhitelistRequirement # DeltaV - Whitelist requirement
- !type:AgeRequirement
requiredAge: 21
weight: 10
@@ -24,6 +23,7 @@
requireAdminNotify: true
supervisors: job-supervisors-captain
canBeAntag: false
+ whitelisted: true # DeltaV
access:
- HeadOfSecurity
- Command