Add job whitelist system (#28085)
* Add job whitelist system * Address reviews * Fix name * Apply suggestions from code review Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com> * cancinium --------- Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
@@ -22,7 +22,6 @@ using Robust.Client.UserInterface.Controllers;
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.Manager;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Lobby;
|
namespace Content.Client.Lobby;
|
||||||
@@ -70,12 +69,9 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
|||||||
_profileEditor?.RefreshFlavorText();
|
_profileEditor?.RefreshFlavorText();
|
||||||
});
|
});
|
||||||
|
|
||||||
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, args =>
|
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
|
||||||
{
|
|
||||||
_profileEditor?.RefreshAntags();
|
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
|
||||||
_profileEditor?.RefreshJobs();
|
|
||||||
_profileEditor?.RefreshLoadouts();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LobbyCharacterPreviewPanel? GetLobbyPreview()
|
private LobbyCharacterPreviewPanel? GetLobbyPreview()
|
||||||
@@ -193,6 +189,13 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
|||||||
PreviewPanel.SetSummaryText(humanoid.Summary);
|
PreviewPanel.SetSummaryText(humanoid.Summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshProfileEditor()
|
||||||
|
{
|
||||||
|
_profileEditor?.RefreshAntags();
|
||||||
|
_profileEditor?.RefreshJobs();
|
||||||
|
_profileEditor?.RefreshLoadouts();
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveProfile()
|
private void SaveProfile()
|
||||||
{
|
{
|
||||||
DebugTools.Assert(EditedProfile != null);
|
DebugTools.Assert(EditedProfile != null);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
|
using Content.Shared.Players.JobWhitelist;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
@@ -24,6 +25,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
|
|
||||||
private readonly Dictionary<string, TimeSpan> _roles = new();
|
private readonly Dictionary<string, TimeSpan> _roles = new();
|
||||||
private readonly List<string> _roleBans = new();
|
private readonly List<string> _roleBans = new();
|
||||||
|
private readonly List<string> _jobWhitelists = new();
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
// Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
|
// Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
|
||||||
_net.RegisterNetMessage<MsgRoleBans>(RxRoleBans);
|
_net.RegisterNetMessage<MsgRoleBans>(RxRoleBans);
|
||||||
_net.RegisterNetMessage<MsgPlayTime>(RxPlayTime);
|
_net.RegisterNetMessage<MsgPlayTime>(RxPlayTime);
|
||||||
|
_net.RegisterNetMessage<MsgJobWhitelist>(RxJobWhitelist);
|
||||||
|
|
||||||
_client.RunLevelChanged += ClientOnRunLevelChanged;
|
_client.RunLevelChanged += ClientOnRunLevelChanged;
|
||||||
}
|
}
|
||||||
@@ -79,6 +82,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
Updated?.Invoke();
|
Updated?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RxJobWhitelist(MsgJobWhitelist message)
|
||||||
|
{
|
||||||
|
_jobWhitelists.Clear();
|
||||||
|
_jobWhitelists.AddRange(message.Whitelist);
|
||||||
|
Updated?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||||
{
|
{
|
||||||
reason = null;
|
reason = null;
|
||||||
@@ -89,6 +99,9 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CheckWhitelist(job, out reason))
|
||||||
|
return false;
|
||||||
|
|
||||||
var player = _playerManager.LocalSession;
|
var player = _playerManager.LocalSession;
|
||||||
if (player == null)
|
if (player == null)
|
||||||
return true;
|
return true;
|
||||||
@@ -116,6 +129,21 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
return reason == null;
|
return reason == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||||
|
{
|
||||||
|
reason = default;
|
||||||
|
if (!_cfg.GetCVar(CCVars.GameRoleWhitelist))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (job.Whitelisted && !_jobWhitelists.Contains(job.ID))
|
||||||
|
{
|
||||||
|
reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan FetchOverallPlaytime()
|
public TimeSpan FetchOverallPlaytime()
|
||||||
{
|
{
|
||||||
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
|
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public static partial class PoolManager
|
|||||||
(CCVars.NPCMaxUpdates.Name, "999999"),
|
(CCVars.NPCMaxUpdates.Name, "999999"),
|
||||||
(CVars.ThreadParallelCount.Name, "1"),
|
(CVars.ThreadParallelCount.Name, "1"),
|
||||||
(CCVars.GameRoleTimers.Name, "false"),
|
(CCVars.GameRoleTimers.Name, "false"),
|
||||||
|
(CCVars.GameRoleWhitelist.Name, "false"),
|
||||||
(CCVars.GridFill.Name, "false"),
|
(CCVars.GridFill.Name, "false"),
|
||||||
(CCVars.PreloadGrids.Name, "false"),
|
(CCVars.PreloadGrids.Name, "false"),
|
||||||
(CCVars.ArrivalsShuttles.Name, "false"),
|
(CCVars.ArrivalsShuttles.Name, "false"),
|
||||||
|
|||||||
1913
Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs
generated
Normal file
1913
Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RoleWhitelist : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "role_whitelists",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
player_user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
role_id = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_role_whitelists_player_player_user_id",
|
||||||
|
column: x => x.player_user_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "role_whitelists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -900,6 +900,22 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("profile_role_loadout", (string)null);
|
b.ToTable("profile_role_loadout", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("PlayerUserId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("player_user_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("role_id");
|
||||||
|
|
||||||
|
b.HasKey("PlayerUserId", "RoleId")
|
||||||
|
.HasName("PK_role_whitelists");
|
||||||
|
|
||||||
|
b.ToTable("role_whitelists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1623,6 +1639,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "Player")
|
||||||
|
.WithMany("JobWhitelists")
|
||||||
|
.HasForeignKey("PlayerUserId")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_role_whitelists_player_player_user_id");
|
||||||
|
|
||||||
|
b.Navigation("Player");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Server", "Server")
|
b.HasOne("Content.Server.Database.Server", "Server")
|
||||||
@@ -1822,6 +1851,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("AdminWatchlistsLastEdited");
|
b.Navigation("AdminWatchlistsLastEdited");
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsReceived");
|
b.Navigation("AdminWatchlistsReceived");
|
||||||
|
|
||||||
|
b.Navigation("JobWhitelists");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||||
|
|||||||
1838
Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs
generated
Normal file
1838
Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RoleWhitelist : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "role_whitelists",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
player_user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
role_id = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_role_whitelists_player_player_user_id",
|
||||||
|
column: x => x.player_user_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "role_whitelists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -847,6 +847,22 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("profile_role_loadout", (string)null);
|
b.ToTable("profile_role_loadout", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("PlayerUserId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("player_user_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("role_id");
|
||||||
|
|
||||||
|
b.HasKey("PlayerUserId", "RoleId")
|
||||||
|
.HasName("PK_role_whitelists");
|
||||||
|
|
||||||
|
b.ToTable("role_whitelists", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1548,6 +1564,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "Player")
|
||||||
|
.WithMany("JobWhitelists")
|
||||||
|
.HasForeignKey("PlayerUserId")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_role_whitelists_player_player_user_id");
|
||||||
|
|
||||||
|
b.Navigation("Player");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Server", "Server")
|
b.HasOne("Content.Server.Database.Server", "Server")
|
||||||
@@ -1747,6 +1776,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("AdminWatchlistsLastEdited");
|
b.Navigation("AdminWatchlistsLastEdited");
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsReceived");
|
b.Navigation("AdminWatchlistsReceived");
|
||||||
|
|
||||||
|
b.Navigation("JobWhitelists");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ namespace Content.Server.Database
|
|||||||
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
||||||
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
|
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
|
||||||
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
|
||||||
|
public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -314,6 +315,13 @@ namespace Content.Server.Database
|
|||||||
.HasForeignKey(ban => ban.LastEditedById)
|
.HasForeignKey(ban => ban.LastEditedById)
|
||||||
.HasPrincipalKey(author => author.UserId)
|
.HasPrincipalKey(author => author.UserId)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
modelBuilder.Entity<RoleWhitelist>()
|
||||||
|
.HasOne(w => w.Player)
|
||||||
|
.WithMany(p => p.JobWhitelists)
|
||||||
|
.HasForeignKey(w => w.PlayerUserId)
|
||||||
|
.HasPrincipalKey(p => p.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||||
@@ -530,6 +538,7 @@ namespace Content.Server.Database
|
|||||||
public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
|
public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
|
||||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
||||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
||||||
|
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Table("whitelist")]
|
[Table("whitelist")]
|
||||||
@@ -1099,4 +1108,15 @@ namespace Content.Server.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Dismissed { get; set; }
|
public bool Dismissed { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PrimaryKey(nameof(PlayerUserId), nameof(RoleId))]
|
||||||
|
public class RoleWhitelist
|
||||||
|
{
|
||||||
|
[Required, ForeignKey("Player")]
|
||||||
|
public Guid PlayerUserId { get; set; }
|
||||||
|
public Player Player { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string RoleId { get; set; } = default!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
214
Content.Server/Administration/Commands/JobWhitelistCommands.cs
Normal file
214
Content.Server/Administration/Commands/JobWhitelistCommands.cs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Server.Players.JobWhitelist;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Commands;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Ban)]
|
||||||
|
public sealed class JobWhitelistAddCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
[Dependency] private readonly JobWhitelistManager _jobWhitelist = default!;
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _players = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
|
||||||
|
public override string Command => "jobwhitelistadd";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||||
|
("properAmount", 2),
|
||||||
|
("currentAmount", args.Length)));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = args[0].Trim();
|
||||||
|
var job = new ProtoId<JobPrototype>(args[1].Trim());
|
||||||
|
if (!_prototypes.TryIndex(job, out var jobPrototype))
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id)));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
var guid = data.UserId;
|
||||||
|
var isWhitelisted = await _db.IsJobWhitelisted(guid, job);
|
||||||
|
if (isWhitelisted)
|
||||||
|
{
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted",
|
||||||
|
("player", player),
|
||||||
|
("jobId", job.Id),
|
||||||
|
("jobName", jobPrototype.LocalizedName)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobWhitelist.AddWhitelist(guid, job);
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added",
|
||||||
|
("player", player),
|
||||||
|
("jobId", job.Id),
|
||||||
|
("jobName", jobPrototype.LocalizedName)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(
|
||||||
|
_players.Sessions.Select(s => s.Name),
|
||||||
|
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length == 2)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(
|
||||||
|
_prototypes.EnumeratePrototypes<JobPrototype>().Select(p => p.ID),
|
||||||
|
Loc.GetString("cmd-jobwhitelist-hint-job"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Ban)]
|
||||||
|
public sealed class GetJobWhitelistCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _players = default!;
|
||||||
|
|
||||||
|
public override string Command => "jobwhitelistget";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
shell.WriteError("This command needs at least one argument.");
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = string.Join(' ', args).Trim();
|
||||||
|
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
var guid = data.UserId;
|
||||||
|
var whitelists = await _db.GetJobWhitelists(guid);
|
||||||
|
if (whitelists.Count == 0)
|
||||||
|
{
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for",
|
||||||
|
("player", player),
|
||||||
|
("jobs", string.Join(", ", whitelists))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(
|
||||||
|
_players.Sessions.Select(s => s.Name),
|
||||||
|
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Ban)]
|
||||||
|
public sealed class RemoveJobWhitelistCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
[Dependency] private readonly JobWhitelistManager _jobWhitelist = default!;
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _players = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
|
||||||
|
public override string Command => "jobwhitelistremove";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific",
|
||||||
|
("properAmount", 2),
|
||||||
|
("currentAmount", args.Length)));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = args[0].Trim();
|
||||||
|
var job = new ProtoId<JobPrototype>(args[1].Trim());
|
||||||
|
if (!_prototypes.TryIndex(job, out var jobPrototype))
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job)));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = await _playerLocator.LookupIdByNameAsync(player);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
var guid = data.UserId;
|
||||||
|
var isWhitelisted = await _db.IsJobWhitelisted(guid, job);
|
||||||
|
if (!isWhitelisted)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted",
|
||||||
|
("player", player),
|
||||||
|
("jobId", job.Id),
|
||||||
|
("jobName", jobPrototype.LocalizedName)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobWhitelist.RemoveWhitelist(guid, job);
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed",
|
||||||
|
("player", player),
|
||||||
|
("jobId", job.Id),
|
||||||
|
("jobName", jobPrototype.LocalizedName)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(
|
||||||
|
_players.Sessions.Select(s => s.Name),
|
||||||
|
Loc.GetString("cmd-jobwhitelist-hint-player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length == 2)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHintOptions(
|
||||||
|
_prototypes.EnumeratePrototypes<JobPrototype>().Select(p => p.ID),
|
||||||
|
Loc.GetString("cmd-jobwhitelist-hint-job"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,7 +73,9 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||||||
|
|
||||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
|
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
|
||||||
{
|
{
|
||||||
return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null;
|
return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans)
|
||||||
|
? roleBans.Select(banDef => banDef.Role).ToHashSet()
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
|
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
|
||||||
@@ -263,13 +265,13 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||||||
return $"Pardoned ban with id {banId}";
|
return $"Pardoned ban with id {banId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<string>? GetJobBans(NetUserId playerUserId)
|
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
|
||||||
{
|
{
|
||||||
if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
|
if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
|
||||||
return null;
|
return null;
|
||||||
return roleBans
|
return roleBans
|
||||||
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
|
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
|
||||||
.Select(ban => ban.Role[JobPrefix.Length..])
|
.Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..]))
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ using System.Collections.Immutable;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Managers;
|
namespace Content.Server.Administration.Managers;
|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ public interface IBanManager
|
|||||||
/// <param name="reason">Reason for the ban</param>
|
/// <param name="reason">Reason for the ban</param>
|
||||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason);
|
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason);
|
||||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
|
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
|
||||||
public HashSet<string>? GetJobBans(NetUserId playerUserId);
|
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a job ban for the specified target, username or GUID
|
/// Creates a job ban for the specified target, username or GUID
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ using Content.Shared.Humanoid;
|
|||||||
using Content.Shared.Humanoid.Markings;
|
using Content.Shared.Humanoid.Markings;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Preferences.Loadouts;
|
using Content.Shared.Preferences.Loadouts;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Database
|
namespace Content.Server.Database
|
||||||
@@ -1579,6 +1581,65 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Job Whitelists
|
||||||
|
|
||||||
|
public async Task<bool> AddJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
var exists = await db.DbContext.RoleWhitelists
|
||||||
|
.Where(w => w.PlayerUserId == player)
|
||||||
|
.Where(w => w.RoleId == job.Id)
|
||||||
|
.AnyAsync();
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var whitelist = new RoleWhitelist
|
||||||
|
{
|
||||||
|
PlayerUserId = player,
|
||||||
|
RoleId = job
|
||||||
|
};
|
||||||
|
db.DbContext.RoleWhitelists.Add(whitelist);
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetJobWhitelists(Guid player, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb(cancel);
|
||||||
|
return await db.DbContext.RoleWhitelists
|
||||||
|
.Where(w => w.PlayerUserId == player)
|
||||||
|
.Select(w => w.RoleId)
|
||||||
|
.ToListAsync(cancellationToken: cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsJobWhitelisted(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
return await db.DbContext.RoleWhitelists
|
||||||
|
.Where(w => w.PlayerUserId == player)
|
||||||
|
.Where(w => w.RoleId == job.Id)
|
||||||
|
.AnyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
var entry = await db.DbContext.RoleWhitelists
|
||||||
|
.Where(w => w.PlayerUserId == player)
|
||||||
|
.Where(w => w.RoleId == job.Id)
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
db.DbContext.RoleWhitelists.Remove(entry);
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
|
||||||
// Normalize DateTimes here so they're always Utc. Thanks.
|
// Normalize DateTimes here so they're always Utc. Thanks.
|
||||||
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Content.Shared.Administration.Logs;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
|
using Content.Shared.Roles;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -17,6 +18,7 @@ using Prometheus;
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using LogLevel = Robust.Shared.Log.LogLevel;
|
using LogLevel = Robust.Shared.Log.LogLevel;
|
||||||
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
|
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||||
|
|
||||||
@@ -290,6 +292,18 @@ namespace Content.Server.Database
|
|||||||
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Job Whitelists
|
||||||
|
|
||||||
|
Task AddJobWhitelist(Guid player, ProtoId<JobPrototype> job);
|
||||||
|
|
||||||
|
|
||||||
|
Task<List<string>> GetJobWhitelists(Guid player, CancellationToken cancel = default);
|
||||||
|
Task<bool> IsJobWhitelisted(Guid player, ProtoId<JobPrototype> job);
|
||||||
|
|
||||||
|
Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ServerDbManager : IServerDbManager
|
public sealed class ServerDbManager : IServerDbManager
|
||||||
@@ -869,6 +883,30 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo));
|
return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task AddJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.AddJobWhitelist(player, job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<string>> GetJobWhitelists(Guid player, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
DbReadOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.GetJobWhitelists(player, cancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> IsJobWhitelisted(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
DbReadOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.IsJobWhitelisted(player, job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
||||||
|
}
|
||||||
|
|
||||||
// Wrapper functions to run DB commands from the thread pool.
|
// Wrapper functions to run DB commands from the thread pool.
|
||||||
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
||||||
// For SQLite, this will also enable read parallelization (within limits).
|
// For SQLite, this will also enable read parallelization (within limits).
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Players.PlayTimeTracking;
|
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -19,11 +17,12 @@ namespace Content.Server.Database;
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class UserDbDataManager : IPostInjectInit
|
public sealed class UserDbDataManager : IPostInjectInit
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
|
|
||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
|
|
||||||
|
|
||||||
private readonly Dictionary<NetUserId, UserData> _users = new();
|
private readonly Dictionary<NetUserId, UserData> _users = new();
|
||||||
|
private readonly List<OnLoadPlayer> _onLoadPlayer = [];
|
||||||
|
private readonly List<OnFinishLoad> _onFinishLoad = [];
|
||||||
|
private readonly List<OnPlayerDisconnect> _onPlayerDisconnect = [];
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -51,8 +50,10 @@ public sealed class UserDbDataManager : IPostInjectInit
|
|||||||
data.Cancel.Cancel();
|
data.Cancel.Cancel();
|
||||||
data.Cancel.Dispose();
|
data.Cancel.Dispose();
|
||||||
|
|
||||||
_prefs.OnClientDisconnected(session);
|
foreach (var onDisconnect in _onPlayerDisconnect)
|
||||||
_playTimeTracking.ClientDisconnected(session);
|
{
|
||||||
|
onDisconnect(session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(ICommonSession session, CancellationToken cancel)
|
private async Task Load(ICommonSession session, CancellationToken cancel)
|
||||||
@@ -62,12 +63,20 @@ public sealed class UserDbDataManager : IPostInjectInit
|
|||||||
// As such, this task must NOT throw a non-cancellation error!
|
// As such, this task must NOT throw a non-cancellation error!
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.WhenAll(
|
var tasks = new List<Task>();
|
||||||
_prefs.LoadData(session, cancel),
|
foreach (var action in _onLoadPlayer)
|
||||||
_playTimeTracking.LoadData(session, cancel));
|
{
|
||||||
|
tasks.Add(action(session, cancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
cancel.ThrowIfCancellationRequested();
|
cancel.ThrowIfCancellationRequested();
|
||||||
_prefs.FinishLoad(session);
|
|
||||||
|
foreach (var action in _onFinishLoad)
|
||||||
|
{
|
||||||
|
action(session);
|
||||||
|
}
|
||||||
|
|
||||||
_sawmill.Verbose($"Load complete for user {session}");
|
_sawmill.Verbose($"Load complete for user {session}");
|
||||||
}
|
}
|
||||||
@@ -118,10 +127,31 @@ public sealed class UserDbDataManager : IPostInjectInit
|
|||||||
return _users[session.UserId].Task;
|
return _users[session.UserId].Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddOnLoadPlayer(OnLoadPlayer action)
|
||||||
|
{
|
||||||
|
_onLoadPlayer.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOnFinishLoad(OnFinishLoad action)
|
||||||
|
{
|
||||||
|
_onFinishLoad.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOnPlayerDisconnect(OnPlayerDisconnect action)
|
||||||
|
{
|
||||||
|
_onPlayerDisconnect.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
void IPostInjectInit.PostInject()
|
void IPostInjectInit.PostInject()
|
||||||
{
|
{
|
||||||
_sawmill = _logManager.GetSawmill("userdb");
|
_sawmill = _logManager.GetSawmill("userdb");
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record UserData(CancellationTokenSource Cancel, Task Task);
|
private sealed record UserData(CancellationTokenSource Cancel, Task Task);
|
||||||
|
|
||||||
|
public delegate Task OnLoadPlayer(ICommonSession player, CancellationToken cancel);
|
||||||
|
|
||||||
|
public delegate void OnFinishLoad(ICommonSession player);
|
||||||
|
|
||||||
|
public delegate void OnPlayerDisconnect(ICommonSession player);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Content.Server.Info;
|
|||||||
using Content.Server.IoC;
|
using Content.Server.IoC;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
|
using Content.Server.Players.JobWhitelist;
|
||||||
using Content.Server.Players.PlayTimeTracking;
|
using Content.Server.Players.PlayTimeTracking;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.ServerInfo;
|
using Content.Server.ServerInfo;
|
||||||
@@ -107,6 +108,7 @@ namespace Content.Server.Entry
|
|||||||
_voteManager.Initialize();
|
_voteManager.Initialize();
|
||||||
_updateManager.Initialize();
|
_updateManager.Initialize();
|
||||||
_playTimeTracking.Initialize();
|
_playTimeTracking.Initialize();
|
||||||
|
IoCManager.Resolve<JobWhitelistManager>().Initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Events;
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct GetDisallowedJobsEvent(ICommonSession Player, HashSet<ProtoId<JobPrototype>> Jobs);
|
||||||
13
Content.Server/GameTicking/Events/IsJobAllowedEvent.cs
Normal file
13
Content.Server/GameTicking/Events/IsJobAllowedEvent.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Events;
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public struct IsJobAllowedEvent(ICommonSession player, ProtoId<JobPrototype> jobId, bool cancelled = false)
|
||||||
|
{
|
||||||
|
public readonly ICommonSession Player = player;
|
||||||
|
public readonly ProtoId<JobPrototype> JobId = jobId;
|
||||||
|
public bool Cancelled = cancelled;
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Server.GameTicking.Events;
|
||||||
using Content.Server.Ghost;
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Spawners.Components;
|
using Content.Server.Spawners.Components;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
@@ -137,8 +137,14 @@ namespace Content.Server.GameTicking
|
|||||||
if (jobBans == null || jobId != null && jobBans.Contains(jobId))
|
if (jobBans == null || jobId != null && jobBans.Contains(jobId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId))
|
if (jobId != null)
|
||||||
|
{
|
||||||
|
var ev = new IsJobAllowedEvent(player, new ProtoId<JobPrototype>(jobId));
|
||||||
|
RaiseLocalEvent(ref ev);
|
||||||
|
if (ev.Cancelled)
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SpawnPlayer(player, character, station, jobId, lateJoin, silent);
|
SpawnPlayer(player, character, station, jobId, lateJoin, silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,10 +187,9 @@ namespace Content.Server.GameTicking
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Figure out job restrictions
|
// Figure out job restrictions
|
||||||
var restrictedRoles = new HashSet<string>();
|
var restrictedRoles = new HashSet<ProtoId<JobPrototype>>();
|
||||||
|
var ev = new GetDisallowedJobsEvent(player, restrictedRoles);
|
||||||
var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player);
|
RaiseLocalEvent(ref ev);
|
||||||
restrictedRoles.UnionWith(getDisallowed);
|
|
||||||
|
|
||||||
var jobBans = _banManager.GetJobBans(player.UserId);
|
var jobBans = _banManager.GetJobBans(player.UserId);
|
||||||
if (jobBans != null)
|
if (jobBans != null)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ using Content.Shared.Roles;
|
|||||||
using Robust.Server;
|
using Robust.Server;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameStates;
|
using Robust.Server.GameStates;
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Content.Server.Info;
|
|||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
using Content.Server.MoMMI;
|
using Content.Server.MoMMI;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
|
using Content.Server.Players.JobWhitelist;
|
||||||
using Content.Server.Players.PlayTimeTracking;
|
using Content.Server.Players.PlayTimeTracking;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.ServerInfo;
|
using Content.Server.ServerInfo;
|
||||||
@@ -61,6 +62,7 @@ namespace Content.Server.IoC
|
|||||||
IoCManager.Register<ServerDbEntryManager>();
|
IoCManager.Register<ServerDbEntryManager>();
|
||||||
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
||||||
IoCManager.Register<ServerApi>();
|
IoCManager.Register<ServerApi>();
|
||||||
|
IoCManager.Register<JobWhitelistManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
Content.Server/Players/JobWhitelist/JobWhitelistManager.cs
Normal file
114
Content.Server/Players/JobWhitelist/JobWhitelistManager.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Players.JobWhitelist;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Content.Server.Players.JobWhitelist;
|
||||||
|
|
||||||
|
public sealed class JobWhitelistManager : IPostInjectInit
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
[Dependency] private readonly INetManager _net = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||||
|
|
||||||
|
private readonly Dictionary<NetUserId, HashSet<string>> _whitelists = new();
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_net.RegisterNetMessage<MsgJobWhitelist>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadData(ICommonSession session, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var whitelists = await _db.GetJobWhitelists(session.UserId, cancel);
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
_whitelists[session.UserId] = whitelists.ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinishLoad(ICommonSession session)
|
||||||
|
{
|
||||||
|
SendJobWhitelist(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientDisconnected(ICommonSession session)
|
||||||
|
{
|
||||||
|
_whitelists.Remove(session.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void AddWhitelist(NetUserId player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
if (_whitelists.TryGetValue(player, out var whitelists))
|
||||||
|
whitelists.Add(job);
|
||||||
|
|
||||||
|
await _db.AddJobWhitelist(player, job);
|
||||||
|
|
||||||
|
if (_player.TryGetSessionById(player, out var session))
|
||||||
|
SendJobWhitelist(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAllowed(ICommonSession session, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
if (!_config.GetCVar(CCVars.GameRoleWhitelist))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!_prototypes.TryIndex(job, out var jobPrototype) ||
|
||||||
|
!jobPrototype.Whitelisted)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsWhitelisted(session.UserId, job);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsWhitelisted(NetUserId player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
if (!_whitelists.TryGetValue(player, out var whitelists))
|
||||||
|
{
|
||||||
|
Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}",
|
||||||
|
player,
|
||||||
|
job,
|
||||||
|
Environment.StackTrace);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelists.Contains(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void RemoveWhitelist(NetUserId player, ProtoId<JobPrototype> job)
|
||||||
|
{
|
||||||
|
_whitelists.GetValueOrDefault(player)?.Remove(job);
|
||||||
|
await _db.RemoveJobWhitelist(player, job);
|
||||||
|
|
||||||
|
if (_player.TryGetSessionById(new NetUserId(player), out var session))
|
||||||
|
SendJobWhitelist(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendJobWhitelist(ICommonSession player)
|
||||||
|
{
|
||||||
|
var msg = new MsgJobWhitelist
|
||||||
|
{
|
||||||
|
Whitelist = _whitelists.GetValueOrDefault(player.UserId) ?? new HashSet<string>()
|
||||||
|
};
|
||||||
|
|
||||||
|
_net.ServerSendMessage(msg, player.Channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPostInjectInit.PostInject()
|
||||||
|
{
|
||||||
|
_userDb.AddOnLoadPlayer(LoadData);
|
||||||
|
_userDb.AddOnFinishLoad(FinishLoad);
|
||||||
|
_userDb.AddOnPlayerDisconnect(ClientDisconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs
Normal file
83
Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Content.Server.GameTicking.Events;
|
||||||
|
using Content.Server.Station.Events;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Players.JobWhitelist;
|
||||||
|
|
||||||
|
public sealed class JobWhitelistSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
|
[Dependency] private readonly JobWhitelistManager _manager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
|
||||||
|
private ImmutableArray<ProtoId<JobPrototype>> _whitelistedJobs = [];
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||||
|
SubscribeLocalEvent<StationJobsGetCandidatesEvent>(OnStationJobsGetCandidates);
|
||||||
|
SubscribeLocalEvent<IsJobAllowedEvent>(OnIsJobAllowed);
|
||||||
|
SubscribeLocalEvent<GetDisallowedJobsEvent>(OnGetDisallowedJobs);
|
||||||
|
|
||||||
|
CacheJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev)
|
||||||
|
{
|
||||||
|
if (ev.WasModified<JobPrototype>())
|
||||||
|
CacheJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev)
|
||||||
|
{
|
||||||
|
if (!_config.GetCVar(CCVars.GameRoleWhitelist))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = ev.Jobs.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var jobId = ev.Jobs[i];
|
||||||
|
if (_player.TryGetSessionById(ev.Player, out var player) &&
|
||||||
|
!_manager.IsAllowed(player, jobId))
|
||||||
|
{
|
||||||
|
ev.Jobs.RemoveSwap(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIsJobAllowed(ref IsJobAllowedEvent ev)
|
||||||
|
{
|
||||||
|
if (!_manager.IsAllowed(ev.Player, ev.JobId))
|
||||||
|
ev.Cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev)
|
||||||
|
{
|
||||||
|
if (!_config.GetCVar(CCVars.GameRoleWhitelist))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var job in _whitelistedJobs)
|
||||||
|
{
|
||||||
|
if (!_manager.IsAllowed(ev.Player, job))
|
||||||
|
ev.Jobs.Add(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheJobs()
|
||||||
|
{
|
||||||
|
var builder = ImmutableArray.CreateBuilder<ProtoId<JobPrototype>>();
|
||||||
|
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
|
||||||
|
{
|
||||||
|
if (job.Whitelisted)
|
||||||
|
builder.Add(job.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
_whitelistedJobs = builder.ToImmutable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ public delegate void CalcPlayTimeTrackersCallback(ICommonSession player, HashSet
|
|||||||
/// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick).
|
/// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick).
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager
|
public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IServerDbManager _db = default!;
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
[Dependency] private readonly IServerNetManager _net = default!;
|
[Dependency] private readonly IServerNetManager _net = default!;
|
||||||
@@ -62,6 +62,7 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager
|
|||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly ITaskManager _task = default!;
|
[Dependency] private readonly ITaskManager _task = default!;
|
||||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||||
|
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -445,4 +446,10 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly HashSet<string> DbTrackersDirty = new();
|
public readonly HashSet<string> DbTrackersDirty = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IPostInjectInit.PostInject()
|
||||||
|
{
|
||||||
|
_userDb.AddOnLoadPlayer(LoadData);
|
||||||
|
_userDb.AddOnPlayerDisconnect(ClientDisconnected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using Content.Server.Administration.Managers;
|
|||||||
using Content.Server.Afk;
|
using Content.Server.Afk;
|
||||||
using Content.Server.Afk.Events;
|
using Content.Server.Afk.Events;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.GameTicking.Events;
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
|
using Content.Server.Station.Events;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Mobs;
|
using Content.Shared.Mobs;
|
||||||
@@ -12,7 +14,6 @@ using Content.Shared.Mobs.Components;
|
|||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -50,6 +51,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
|
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
|
||||||
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
||||||
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
|
||||||
|
SubscribeLocalEvent<StationJobsGetCandidatesEvent>(OnStationJobsGetCandidates);
|
||||||
|
SubscribeLocalEvent<IsJobAllowedEvent>(OnIsJobAllowed);
|
||||||
|
SubscribeLocalEvent<GetDisallowedJobsEvent>(OnGetDisallowedJobs);
|
||||||
_adminManager.OnPermsChanged += AdminPermsChanged;
|
_adminManager.OnPermsChanged += AdminPermsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +178,22 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
_tracking.QueueSendTimers(ev.PlayerSession);
|
_tracking.QueueSendTimers(ev.PlayerSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev)
|
||||||
|
{
|
||||||
|
RemoveDisallowedJobs(ev.Player, ev.Jobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIsJobAllowed(ref IsJobAllowedEvent ev)
|
||||||
|
{
|
||||||
|
if (!IsAllowed(ev.Player, ev.JobId))
|
||||||
|
ev.Cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev)
|
||||||
|
{
|
||||||
|
ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player));
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsAllowed(ICommonSession player, string role)
|
public bool IsAllowed(ICommonSession player, string role)
|
||||||
{
|
{
|
||||||
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
|
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
|
||||||
@@ -190,9 +210,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes);
|
return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<string> GetDisallowedJobs(ICommonSession player)
|
public HashSet<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
|
||||||
{
|
{
|
||||||
var roles = new HashSet<string>();
|
var roles = new HashSet<ProtoId<JobPrototype>>();
|
||||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||||
return roles;
|
return roles;
|
||||||
|
|
||||||
@@ -222,7 +242,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveDisallowedJobs(NetUserId userId, ref List<string> jobs)
|
public void RemoveDisallowedJobs(NetUserId userId, List<ProtoId<JobPrototype>> jobs)
|
||||||
{
|
{
|
||||||
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
|
||||||
return;
|
return;
|
||||||
@@ -239,7 +259,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var job = jobs[i];
|
var job = jobs[i];
|
||||||
|
|
||||||
if (!_prototypes.TryIndex<JobPrototype>(job, out var jobber) ||
|
if (!_prototypes.TryIndex(job, out var jobber) ||
|
||||||
jobber.Requirements == null ||
|
jobber.Requirements == null ||
|
||||||
jobber.Requirements.Count == 0)
|
jobber.Requirements.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -3,26 +3,21 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.Humanoid;
|
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Humanoid.Prototypes;
|
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Server.Preferences.Managers
|
namespace Content.Server.Preferences.Managers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends <see cref="MsgPreferencesAndSettings"/> before the client joins the lobby.
|
/// Sends <see cref="MsgPreferencesAndSettings"/> before the client joins the lobby.
|
||||||
/// Receives <see cref="MsgSelectCharacter"/> and <see cref="MsgUpdateCharacter"/> at any time.
|
/// Receives <see cref="MsgSelectCharacter"/> and <see cref="MsgUpdateCharacter"/> at any time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ServerPreferencesManager : IServerPreferencesManager
|
public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
@@ -30,6 +25,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||||
[Dependency] private readonly ILogManager _log = default!;
|
[Dependency] private readonly ILogManager _log = default!;
|
||||||
|
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||||
|
|
||||||
// Cache player prefs on the server so we don't need as much async hell related to them.
|
// Cache player prefs on the server so we don't need as much async hell related to them.
|
||||||
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
||||||
@@ -326,5 +322,12 @@ namespace Content.Server.Preferences.Managers
|
|||||||
public bool PrefsLoaded;
|
public bool PrefsLoaded;
|
||||||
public PlayerPreferences? Prefs;
|
public PlayerPreferences? Prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IPostInjectInit.PostInject()
|
||||||
|
{
|
||||||
|
_userDb.AddOnLoadPlayer(LoadData);
|
||||||
|
_userDb.AddOnFinishLoad(FinishLoad);
|
||||||
|
_userDb.AddOnPlayerDisconnect(OnClientDisconnected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.Roles;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Station.Events;
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct StationJobsGetCandidatesEvent(NetUserId Player, List<ProtoId<JobPrototype>> Jobs);
|
||||||
@@ -2,6 +2,7 @@ using System.Linq;
|
|||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Players.PlayTimeTracking;
|
using Content.Server.Players.PlayTimeTracking;
|
||||||
using Content.Server.Station.Components;
|
using Content.Server.Station.Components;
|
||||||
|
using Content.Server.Station.Events;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -342,8 +343,9 @@ public sealed partial class StationJobsSystem
|
|||||||
foreach (var (player, profile) in profiles)
|
foreach (var (player, profile) in profiles)
|
||||||
{
|
{
|
||||||
var roleBans = _banManager.GetJobBans(player);
|
var roleBans = _banManager.GetJobBans(player);
|
||||||
var profileJobs = profile.JobPriorities.Keys.ToList();
|
var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId<JobPrototype>(k)).ToList();
|
||||||
_playTime.RemoveDisallowedJobs(player, ref profileJobs);
|
var ev = new StationJobsGetCandidatesEvent(player, profileJobs);
|
||||||
|
RaiseLocalEvent(ref ev);
|
||||||
|
|
||||||
List<string>? availableJobs = null;
|
List<string>? availableJobs = null;
|
||||||
|
|
||||||
@@ -354,7 +356,7 @@ public sealed partial class StationJobsSystem
|
|||||||
if (!(priority == selectedPriority || selectedPriority is null))
|
if (!(priority == selectedPriority || selectedPriority is null))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job))
|
if (!_prototypeManager.TryIndex(jobId, out var job))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (weight is not null && job.Weight != weight.Value)
|
if (weight is not null && job.Weight != weight.Value)
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ public sealed partial class StationJobsSystem : EntitySystem
|
|||||||
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
|
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
|
||||||
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
|
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
|
||||||
/// <returns>The selected job, if any.</returns>
|
/// <returns>The selected job, if any.</returns>
|
||||||
public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<string, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<string>? disallowedJobs = null)
|
public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary<string, JobPriority> jobPriorities, bool pickOverflows, IReadOnlySet<ProtoId<JobPrototype>>? disallowedJobs = null)
|
||||||
{
|
{
|
||||||
if (station == EntityUid.Invalid)
|
if (station == EntityUid.Invalid)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -225,6 +225,12 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<bool>
|
public static readonly CVarDef<bool>
|
||||||
GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED);
|
GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If roles should be restricted based on whether or not they are whitelisted.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<bool>
|
||||||
|
GameRoleWhitelist = CVarDef.Create("game.role_whitelist", true, CVar.SERVER | CVar.REPLICATED);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect.
|
/// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
33
Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs
Normal file
33
Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Players.JobWhitelist;
|
||||||
|
|
||||||
|
public sealed class MsgJobWhitelist : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;
|
||||||
|
|
||||||
|
public HashSet<string> Whitelist = new();
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
var count = buffer.ReadVariableInt32();
|
||||||
|
Whitelist.EnsureCapacity(count);
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Whitelist.Add(buffer.ReadString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
buffer.WriteVariableInt32(Whitelist.Count);
|
||||||
|
|
||||||
|
foreach (var ban in Whitelist)
|
||||||
|
{
|
||||||
|
buffer.Write(ban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
|
||||||
|
|
||||||
namespace Content.Shared.Roles
|
namespace Content.Shared.Roles
|
||||||
{
|
{
|
||||||
@@ -116,6 +114,9 @@ namespace Content.Shared.Roles
|
|||||||
|
|
||||||
[DataField("extendedAccessGroups")]
|
[DataField("extendedAccessGroups")]
|
||||||
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
|
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public bool Whitelisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
20
Resources/Locale/en-US/commands/job-whitelist-command.ftl
Normal file
20
Resources/Locale/en-US/commands/job-whitelist-command.ftl
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
cmd-jobwhitelist-job-does-not-exist = Job {$job} does not exist.
|
||||||
|
cmd-jobwhitelist-player-not-found = Player {$player} not found.
|
||||||
|
cmd-jobwhitelist-hint-player = [player]
|
||||||
|
cmd-jobwhitelist-hint-job = [job]
|
||||||
|
|
||||||
|
cmd-jobwhitelistadd-desc = Lets a player play a whitelisted job.
|
||||||
|
cmd-jobwhitelistadd-help = Usage: jobwhitelistadd <username> <job>
|
||||||
|
cmd-jobwhitelistadd-already-whitelisted = {$player} is already whitelisted to play as {$jobId} .({$jobName}).
|
||||||
|
cmd-jobwhitelistadd-added = Added {$player} to the {$jobId} ({$jobName}) whitelist.
|
||||||
|
|
||||||
|
cmd-jobwhitelistget-desc = Gets all the jobs that a player has been whitelisted for.
|
||||||
|
cmd-jobwhitelistget-help = Usage: jobwhitelistadd <username>
|
||||||
|
cmd-jobwhitelistget-whitelisted-none = Player {$player} is not whitelisted for any jobs.
|
||||||
|
cmd-jobwhitelistget-whitelisted-for = "Player {$player} is whitelisted for:
|
||||||
|
{$jobs}"
|
||||||
|
|
||||||
|
cmd-jobwhitelistremove-desc = Removes a player's ability to play a whitelisted job.
|
||||||
|
cmd-jobwhitelistremove-help = Usage: jobwhitelistadd <username> <job>
|
||||||
|
cmd-jobwhitelistremove-was-not-whitelisted = {$player} was not whitelisted to play as {$jobId} ({$jobName}).
|
||||||
|
cmd-jobwhitelistremove-removed = Removed {$player} from the whitelist for {$jobId} ({$jobName}).
|
||||||
1
Resources/Locale/en-US/job/role-whitelist.ftl
Normal file
1
Resources/Locale/en-US/job/role-whitelist.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
role-not-whitelisted = You are not whitelisted to play this role.
|
||||||
Reference in New Issue
Block a user