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>
This commit is contained in:
deltanedas 2024-08-28 00:33:30 +00:00 committed by GitHub
parent a3bef2b30f
commit 7fd2541aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 345 additions and 3 deletions

View File

@ -31,6 +31,7 @@
<Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
<controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
</GridContainer>
<Button Name="JobWhitelistsButton" Text="{Loc player-panel-job-whitelists}" SetWidth="136" SetHeight="27" Disabled="True"/> <!-- DeltaV: Job whitelists -->
</BoxContainer>
</BoxContainer>
</ui:FancyWindow>

View File

@ -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<NetUserId?>? 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
}
}

View File

@ -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());
}

View File

@ -0,0 +1,11 @@
<PanelContainer
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.DeltaV.Administration.UI"
StyleClasses="BackgroundDark"
HorizontalExpand="True"
Margin="4">
<BoxContainer Orientation="Vertical">
<CheckBox Name="Department"/> <!-- Toggles all jobs in the department at once -->
<GridContainer Name="JobsContainer" Columns="4"/> <!-- Populated with each job to toggle individually-->
</BoxContainer>
</PanelContainer>

View File

@ -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<ProtoId<JobPrototype>, bool>? OnSetJob;
public DepartmentWhitelistPanel(DepartmentPrototype department, IPrototypeManager proto, HashSet<ProtoId<JobPrototype>> 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);
}
};
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc player-panel-job-whitelists}" MinSize="750 600">
<BoxContainer Orientation="Vertical">
<Label Name="PlayerName" Margin="4"/>
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="Departments" Orientation="Vertical"/> <!-- Populated with DepartmentWhitelistPanel -->
</ScrollContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@ -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;
/// <summary>
/// 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.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class JobWhitelistsWindow : FancyWindow
{
[Dependency] private readonly IPrototypeManager _proto = default!;
public Action<ProtoId<JobPrototype>, 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<DepartmentPrototype>())
{
var panel = new DepartmentWhitelistPanel(proto, _proto, state.Whitelists);
panel.OnSetJob += (id, whitelisting) => OnSetJob?.Invoke(id, whitelisting);
Departments.AddChild(panel);
}
}
}

View File

@ -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;
/// <summary>
/// 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.
/// </summary>
[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);
}
}

View File

@ -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<ProtoId<JobPrototype>> 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<JobPrototype>(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<JobPrototype>(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();
}
}

View File

@ -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);
}

View File

@ -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<ProtoId<JobPrototype>> Whitelists;
public JobWhitelistsEuiState(string playerName, HashSet<ProtoId<JobPrototype>> whitelists)
{
PlayerName = playerName;
Whitelists = whitelists;
}
}
/// <summary>
/// Tries to add or remove a whitelist of a job for a player.
/// </summary>
[Serializable, NetSerializable]
public sealed class SetJobWhitelistedMessage : EuiMessageBase
{
public ProtoId<JobPrototype> Job;
public bool Whitelisting;
public SetJobWhitelistedMessage(ProtoId<JobPrototype> job, bool whitelisting)
{
Job = job;
Whitelisting = whitelisting;
}
}

View File

@ -9,6 +9,9 @@ namespace Content.Shared.Roles;
/// <summary>
/// Requires the player be globally whitelisted to play a role.
/// </summary>
/// <remarks>
/// Don't use this for jobs, use <c>whitelisted: true</c> on the JobPrototype instead.
/// </remarks>
[Serializable, NetSerializable]
public sealed partial class WhitelistRequirement : JobRequirement
{

View File

@ -0,0 +1 @@
player-panel-job-whitelists = Job Whitelists

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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