using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.Manager;
using Robust.Shared.Utility;
namespace Content.Client.Lobby;
_profileEditor?.RefreshFlavorText();
});
- _configurationManager.OnValueChanged(CCVars.GameRoleTimers, args =>
- {
- _profileEditor?.RefreshAntags();
- _profileEditor?.RefreshJobs();
- _profileEditor?.RefreshLoadouts();
- });
+ _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
+
+ _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
}
private LobbyCharacterPreviewPanel? GetLobbyPreview()
PreviewPanel.SetSummaryText(humanoid.Summary);
}
+ private void RefreshProfileEditor()
+ {
+ _profileEditor?.RefreshAntags();
+ _profileEditor?.RefreshJobs();
+ _profileEditor?.RefreshLoadouts();
+ }
+
private void SaveProfile()
{
DebugTools.Assert(EditedProfile != null);
using System.Diagnostics.CodeAnalysis;
using Content.Shared.CCVar;
using Content.Shared.Players;
+using Content.Shared.Players.JobWhitelist;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
using Robust.Client;
private readonly Dictionary<string, TimeSpan> _roles = new();
private readonly List<string> _roleBans = new();
+ private readonly List<string> _jobWhitelists = new();
private ISawmill _sawmill = default!;
// Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
_net.RegisterNetMessage<MsgRoleBans>(RxRoleBans);
_net.RegisterNetMessage<MsgPlayTime>(RxPlayTime);
+ _net.RegisterNetMessage<MsgJobWhitelist>(RxJobWhitelist);
_client.RunLevelChanged += ClientOnRunLevelChanged;
}
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)
{
reason = null;
return false;
}
+ if (!CheckWhitelist(job, out reason))
+ return false;
+
var player = _playerManager.LocalSession;
if (player == null)
return true;
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()
{
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
(CCVars.NPCMaxUpdates.Name, "999999"),
(CVars.ThreadParallelCount.Name, "1"),
(CCVars.GameRoleTimers.Name, "false"),
+ (CCVars.GameRoleWhitelist.Name, "false"),
(CCVars.GridFill.Name, "false"),
(CCVars.PreloadGrids.Name, "false"),
(CCVars.ArrivalsShuttles.Name, "false"),
--- /dev/null
+// <auto-generated />
+using System;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20240531011555_RoleWhitelist")]
+ partial class RoleWhitelist
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<short>("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property<JsonDocument>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("boolean")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<IPAddress>("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property<IPAddress>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property<JsonDocument>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("integer")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ 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 =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime?>("StartDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("integer")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_hit_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_role_ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet?>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("role_unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("trait_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uploaded_resource_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("integer")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("integer")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ 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 =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+#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");
+ }
+ }
+}
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 =>
{
b.Property<int>("Id")
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 =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("AdminWatchlistsLastEdited");
b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
--- /dev/null
+// <auto-generated />
+using System;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ [DbContext(typeof(SqliteServerDbContext))]
+ [Migration("20240531011549_RoleWhitelist")]
+ partial class RoleWhitelist
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_flag_id");
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("INTEGER")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<sbyte>("Impact")
+ .HasColumnType("INTEGER")
+ .HasColumnName("impact");
+
+ b.Property<string>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_messages_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("INTEGER")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("INTEGER")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_notes_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("INTEGER")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_flag_id");
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_watchlists_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("antag_id");
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("assigned_user_id_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_log_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("INTEGER")
+ .HasColumnName("denied");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("job_id");
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("play_time_id");
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("TEXT")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("player_id");
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_read_rules");
+
+ b.Property<string>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_address");
+
+ b.Property<byte[]>("LastSeenHWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("last_seen_hwid");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_ooc_color");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<int>("Age")
+ .HasColumnType("INTEGER")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_name");
+
+ b.Property<byte[]>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("INTEGER")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_id");
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ 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 =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("ServerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime?>("StartDate")
+ .HasColumnType("TEXT")
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_ban_round_id");
+
+ b.ToTable("server_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_hit_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_role_ban_id");
+
+ b.Property<string>("Address")
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<byte[]>("HWId")
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_ban");
+
+ b.HasIndex("Address");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_server_role_ban_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_server_role_ban_round_id");
+
+ b.ToTable("server_role_ban", null, t =>
+ {
+ t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("role_unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_role_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_role_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_server_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("server_unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("trait_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uploaded_resource_log_id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ 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 =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerRoleBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerRoleBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_server_role_ban_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ {
+ b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_unban_server_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminServerRoleBansCreated");
+
+ b.Navigation("AdminServerRoleBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
+ {
+ b.Navigation("BanHits");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
+ {
+ b.Navigation("Unban");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+#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");
+ }
+ }
+}
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 =>
{
b.Property<int>("Id")
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 =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("AdminWatchlistsLastEdited");
b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
+ public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
.HasForeignKey(ban => ban.LastEditedById)
.HasPrincipalKey(author => author.UserId)
.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 List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
+ public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
}
[Table("whitelist")]
/// </summary>
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!;
+ }
}
--- /dev/null
+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;
+ }
+}
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)
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))
return null;
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
- .Select(ban => ban.Role[JobPrefix.Length..])
+ .Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..]))
.ToHashSet();
}
#endregion
using System.Net;
using System.Threading.Tasks;
using Content.Shared.Database;
+using Content.Shared.Roles;
using Robust.Shared.Network;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Managers;
/// <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 HashSet<string>? GetRoleBans(NetUserId playerUserId);
- public HashSet<string>? GetJobBans(NetUserId playerUserId);
+ public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId);
/// <summary>
/// Creates a job ban for the specified target, username or GUID
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
+using Content.Shared.Roles;
using Microsoft.EntityFrameworkCore;
using Robust.Shared.Enums;
using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Database
#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.
// Normalize DateTimes here so they're always Utc. Thanks.
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Preferences;
+using Content.Shared.Roles;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
using LogLevel = Robust.Shared.Log.LogLevel;
using MSLogLevel = Microsoft.Extensions.Logging.LogLevel;
Task MarkMessageAsSeen(int id, bool dismissedToo);
#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
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.
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
// For SQLite, this will also enable read parallelization (within limits).
using System.Threading;
using System.Threading.Tasks;
-using Content.Server.Players.PlayTimeTracking;
using Content.Server.Preferences.Managers;
-using Robust.Server.Player;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Utility;
/// </remarks>
public sealed class UserDbDataManager : IPostInjectInit
{
- [Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly ILogManager _logManager = default!;
- [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
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!;
data.Cancel.Cancel();
data.Cancel.Dispose();
- _prefs.OnClientDisconnected(session);
- _playTimeTracking.ClientDisconnected(session);
+ foreach (var onDisconnect in _onPlayerDisconnect)
+ {
+ onDisconnect(session);
+ }
}
private async Task Load(ICommonSession session, CancellationToken cancel)
// As such, this task must NOT throw a non-cancellation error!
try
{
- await Task.WhenAll(
- _prefs.LoadData(session, cancel),
- _playTimeTracking.LoadData(session, cancel));
+ var tasks = new List<Task>();
+ foreach (var action in _onLoadPlayer)
+ {
+ tasks.Add(action(session, cancel));
+ }
+
+ await Task.WhenAll(tasks);
cancel.ThrowIfCancellationRequested();
- _prefs.FinishLoad(session);
+
+ foreach (var action in _onFinishLoad)
+ {
+ action(session);
+ }
_sawmill.Verbose($"Load complete for user {session}");
}
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()
{
_sawmill = _logManager.GetSawmill("userdb");
}
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);
}
using Content.Server.IoC;
using Content.Server.Maps;
using Content.Server.NodeContainer.NodeGroups;
+using Content.Server.Players.JobWhitelist;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Preferences.Managers;
using Content.Server.ServerInfo;
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();
+ IoCManager.Resolve<JobWhitelistManager>().Initialize();
}
}
--- /dev/null
+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);
--- /dev/null
+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;
+}
using System.Linq;
using System.Numerics;
using Content.Server.Administration.Managers;
+using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Spawners.Components;
using Content.Server.Speech.Components;
using Content.Server.Station.Components;
-using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Mind;
using Content.Shared.Players;
if (jobBans == null || jobId != null && jobBans.Contains(jobId))
return;
- if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId))
- return;
+ if (jobId != null)
+ {
+ var ev = new IsJobAllowedEvent(player, new ProtoId<JobPrototype>(jobId));
+ RaiseLocalEvent(ref ev);
+ if (ev.Cancelled)
+ return;
+ }
+
SpawnPlayer(player, character, station, jobId, lateJoin, silent);
}
}
// Figure out job restrictions
- var restrictedRoles = new HashSet<string>();
-
- var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player);
- restrictedRoles.UnionWith(getDisallowed);
+ var restrictedRoles = new HashSet<ProtoId<JobPrototype>>();
+ var ev = new GetDisallowedJobsEvent(player, restrictedRoles);
+ RaiseLocalEvent(ref ev);
var jobBans = _banManager.GetJobBans(player.UserId);
if (jobBans != null)
using Robust.Server;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Content.Server.Maps;
using Content.Server.MoMMI;
using Content.Server.NodeContainer.NodeGroups;
+using Content.Server.Players.JobWhitelist;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Preferences.Managers;
using Content.Server.ServerInfo;
IoCManager.Register<ServerDbEntryManager>();
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
IoCManager.Register<ServerApi>();
+ IoCManager.Register<JobWhitelistManager>();
}
}
}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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();
+ }
+}
/// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick).
/// </para>
/// </remarks>
-public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager
+public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit
{
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IServerNetManager _net = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ITaskManager _task = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
+ [Dependency] private readonly UserDbDataManager _userDb = default!;
private ISawmill _sawmill = default!;
/// </summary>
public readonly HashSet<string> DbTrackersDirty = new();
}
+
+ void IPostInjectInit.PostInject()
+ {
+ _userDb.AddOnLoadPlayer(LoadData);
+ _userDb.AddOnPlayerDisconnect(ClientDisconnected);
+ }
}
using Content.Server.Afk;
using Content.Server.Afk.Events;
using Content.Server.GameTicking;
+using Content.Server.GameTicking.Events;
using Content.Server.Mind;
+using Content.Server.Station.Events;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Mobs;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
-using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
+ SubscribeLocalEvent<StationJobsGetCandidatesEvent>(OnStationJobsGetCandidates);
+ SubscribeLocalEvent<IsJobAllowedEvent>(OnIsJobAllowed);
+ SubscribeLocalEvent<GetDisallowedJobsEvent>(OnGetDisallowedJobs);
_adminManager.OnPermsChanged += AdminPermsChanged;
}
_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)
{
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
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))
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))
return;
{
var job = jobs[i];
- if (!_prototypes.TryIndex<JobPrototype>(job, out var jobber) ||
+ if (!_prototypes.TryIndex(job, out var jobber) ||
jobber.Requirements == null ||
jobber.Requirements.Count == 0)
continue;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Database;
-using Content.Server.Humanoid;
using Content.Shared.CCVar;
-using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
-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 Robust.Shared.Utility;
-
namespace Content.Server.Preferences.Managers
{
/// <summary>
/// Sends <see cref="MsgPreferencesAndSettings"/> before the client joins the lobby.
/// Receives <see cref="MsgSelectCharacter"/> and <see cref="MsgUpdateCharacter"/> at any time.
/// </summary>
- public sealed class ServerPreferencesManager : IServerPreferencesManager
+ public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit
{
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IDependencyCollection _dependencies = 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.
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
public bool PrefsLoaded;
public PlayerPreferences? Prefs;
}
+
+ void IPostInjectInit.PostInject()
+ {
+ _userDb.AddOnLoadPlayer(LoadData);
+ _userDb.AddOnFinishLoad(FinishLoad);
+ _userDb.AddOnPlayerDisconnect(OnClientDisconnected);
+ }
}
}
--- /dev/null
+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);
using Content.Server.Administration.Managers;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Station.Components;
+using Content.Server.Station.Events;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Shared.Network;
foreach (var (player, profile) in profiles)
{
var roleBans = _banManager.GetJobBans(player);
- var profileJobs = profile.JobPriorities.Keys.ToList();
- _playTime.RemoveDisallowedJobs(player, ref profileJobs);
+ var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId<JobPrototype>(k)).ToList();
+ var ev = new StationJobsGetCandidatesEvent(player, profileJobs);
+ RaiseLocalEvent(ref ev);
List<string>? availableJobs = null;
if (!(priority == selectedPriority || selectedPriority is null))
continue;
- if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job))
+ if (!_prototypeManager.TryIndex(jobId, out var job))
continue;
if (weight is not null && job.Weight != weight.Value)
/// <param name="pickOverflows">Whether or not to pick from the overflow list.</param>
/// <param name="disallowedJobs">A set of disallowed jobs, if any.</param>
/// <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)
return null;
public static readonly CVarDef<bool>
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>
/// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect.
/// </summary>
--- /dev/null
+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);
+ }
+ }
+}
using Content.Shared.Access;
using Content.Shared.Players.PlayTimeTracking;
-using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Roles
{
[DataField("extendedAccessGroups")]
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
+
+ [DataField]
+ public bool Whitelisted;
}
/// <summary>
--- /dev/null
+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}).
--- /dev/null
+role-not-whitelisted = You are not whitelisted to play this role.