diff --git a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs index ba087718583e06..092a0071fb7064 100644 --- a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs +++ b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs @@ -88,8 +88,9 @@ public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUser button.Disabled = !interfaceEnabled; if (interfaceEnabled) { - button.Pressed = state.TargetAccessReaderIdAccessList?.Contains(accessName) ?? false; - button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true; + // Explicit cast because Rider gives a false error otherwise. + button.Pressed = state.TargetAccessReaderIdAccessList?.Contains((ProtoId) accessName) ?? false; + button.Disabled = (!state.AllowedModifyAccessList?.Contains((ProtoId) accessName)) ?? true; } } } diff --git a/Content.Client/Administration/UI/PermissionsEui.cs b/Content.Client/Administration/UI/PermissionsEui.cs index 4cddf8887eee16..fe1237d4c90959 100644 --- a/Content.Client/Administration/UI/PermissionsEui.cs +++ b/Content.Client/Administration/UI/PermissionsEui.cs @@ -130,6 +130,7 @@ private void SaveAdminPressed(EditAdminWindow popup) } var title = string.IsNullOrWhiteSpace(popup.TitleEdit.Text) ? null : popup.TitleEdit.Text; + var suspended = popup.SuspendedCheckbox.Pressed; if (popup.SourceData is { } src) { @@ -139,7 +140,8 @@ private void SaveAdminPressed(EditAdminWindow popup) Title = title, PosFlags = pos, NegFlags = neg, - RankId = rank + RankId = rank, + Suspended = suspended, }); } else @@ -152,7 +154,8 @@ private void SaveAdminPressed(EditAdminWindow popup) Title = title, PosFlags = pos, NegFlags = neg, - RankId = rank + RankId = rank, + Suspended = suspended, }); } @@ -171,7 +174,7 @@ private void SaveAdminRankPressed(EditAdminRankWindow popup) { Id = src, Flags = flags, - Name = name + Name = name, }); } else @@ -351,6 +354,7 @@ private sealed class EditAdminWindow : DefaultWindow public readonly OptionButton RankButton; public readonly Button SaveButton; public readonly Button? RemoveButton; + public readonly CheckBox SuspendedCheckbox; public readonly Dictionary FlagButtons = new(); @@ -381,6 +385,12 @@ public EditAdminWindow(PermissionsEui ui, PermissionsEuiState.AdminData? data) RankButton = new OptionButton(); SaveButton = new Button { Text = Loc.GetString("permissions-eui-edit-admin-window-save-button"), HorizontalAlignment = HAlignment.Right }; + SuspendedCheckbox = new CheckBox + { + Text = Loc.GetString("permissions-eui-edit-admin-window-suspended"), + Pressed = data?.Suspended ?? false, + }; + RankButton.AddItem(Loc.GetString("permissions-eui-edit-admin-window-no-rank-button"), NoRank); foreach (var (rId, rank) in ui._ranks) { @@ -488,7 +498,8 @@ public EditAdminWindow(PermissionsEui ui, PermissionsEuiState.AdminData? data) { nameControl, TitleEdit, - RankButton + RankButton, + SuspendedCheckbox, } }, permGrid diff --git a/Content.Client/Program.cs b/Content.Client/Program.cs index f48c1e73c3155c..0404e825cffbfe 100644 --- a/Content.Client/Program.cs +++ b/Content.Client/Program.cs @@ -4,6 +4,7 @@ namespace Content.Client { internal static class Program { + [STAThread] public static void Main(string[] args) { ContentStart.Start(args); diff --git a/Content.Packaging/ClientPackaging.cs b/Content.Packaging/ClientPackaging.cs index a66d4ec5b91403..6d0a46279076d4 100644 --- a/Content.Packaging/ClientPackaging.cs +++ b/Content.Packaging/ClientPackaging.cs @@ -61,7 +61,13 @@ public static async Task WriteResources( var graph = new RobustClientAssetGraph(); pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name)); - AssetGraph.CalculateGraph(graph.AllPasses.Append(pass).ToArray(), logger); + var dropSvgPass = new AssetPassFilterDrop(f => f.Path.EndsWith(".svg")) + { + Name = "DropSvgPass", + }; + dropSvgPass.AddDependency(graph.Input).AddBefore(graph.PresetPasses); + + AssetGraph.CalculateGraph([pass, dropSvgPass, ..graph.AllPasses], logger); var inputPass = graph.Input; @@ -72,7 +78,7 @@ await RobustSharedPackaging.WriteContentAssemblies( new[] { "Content.Client", "Content.Shared", "Content.Shared.Database" }, cancel: cancel); - await RobustClientPackaging.WriteClientResources(contentDir, pass, cancel); + await RobustClientPackaging.WriteClientResources(contentDir, inputPass, cancel); inputPass.InjectFinished(); } diff --git a/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs b/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs new file mode 100644 index 00000000000000..364ded06d01c7c --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs @@ -0,0 +1,2084 @@ +// +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("20241223235939_AdminStatus")] + partial class AdminStatus + { + /// + 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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Deadminned") + .HasColumnType("boolean") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("boolean") + .HasColumnName("suspended"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("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("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("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("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("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.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("real") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("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("Time"); + + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("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("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + 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.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("integer") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + 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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + 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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + 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 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.cs b/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.cs new file mode 100644 index 00000000000000..8444cead4ef0c7 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class AdminStatus : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "deadminned", + table: "admin", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "suspended", + table: "admin", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "deadminned", + table: "admin"); + + migrationBuilder.DropColumn( + name: "suspended", + table: "admin"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index ef0a7bcb25489f..e528cb70fe819e 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -36,6 +36,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("integer") .HasColumnName("admin_rank_id"); + b.Property("Deadminned") + .HasColumnType("boolean") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("boolean") + .HasColumnName("suspended"); + b.Property("Title") .HasColumnType("text") .HasColumnName("title"); diff --git a/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs new file mode 100644 index 00000000000000..3f5f556ca1b9e8 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs @@ -0,0 +1,2007 @@ +// +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("20241223235932_AdminStatus")] + partial class AdminStatus + { + /// + 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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Deadminned") + .HasColumnType("INTEGER") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("INTEGER") + .HasColumnName("suspended"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("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("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("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("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("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.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Blacklist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_blacklist"); + + b.ToTable("blacklist", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("Trust") + .HasColumnType("REAL") + .HasColumnName("trust"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("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("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("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("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("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("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ConnectionLogId") + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ConnectionLogId"); + + b1.ToTable("connection_log"); + + b1.WithOwner() + .HasForeignKey("ConnectionLogId") + .HasConstraintName("FK_connection_log_connection_log_connection_log_id"); + }); + + b.Navigation("HWId"); + + 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.Player", b => + { + b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 => + { + b1.Property("PlayerId") + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("last_seen_hwid_type"); + + b1.HasKey("PlayerId"); + + b1.ToTable("player"); + + b1.WithOwner() + .HasForeignKey("PlayerId") + .HasConstraintName("FK_player_player_player_id"); + }); + + b.Navigation("LastSeenHWId"); + }); + + 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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerBanId"); + + b1.ToTable("server_ban"); + + b1.WithOwner() + .HasForeignKey("ServerBanId") + .HasConstraintName("FK_server_ban_server_ban_server_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + 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.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("ServerRoleBanId") + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("hwid_type"); + + b1.HasKey("ServerRoleBanId"); + + b1.ToTable("server_role_ban"); + + b1.WithOwner() + .HasForeignKey("ServerRoleBanId") + .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id"); + }); + + b.Navigation("CreatedBy"); + + b.Navigation("HWId"); + + 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 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.cs b/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.cs new file mode 100644 index 00000000000000..ac08aa246ec70d --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class AdminStatus : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "deadminned", + table: "admin", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "suspended", + table: "admin", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "deadminned", + table: "admin"); + + migrationBuilder.DropColumn( + name: "suspended", + table: "admin"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index 14516a0122156a..370fcc13ca333e 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -28,6 +28,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER") .HasColumnName("admin_rank_id"); + b.Property("Deadminned") + .HasColumnType("INTEGER") + .HasColumnName("deadminned"); + + b.Property("Suspended") + .HasColumnType("INTEGER") + .HasColumnName("suspended"); + b.Property("Title") .HasColumnType("TEXT") .HasColumnName("title"); diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 25283cacc99784..885bba0ffc270c 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -610,6 +610,16 @@ public class Admin [Key] public Guid UserId { get; set; } public string? Title { get; set; } + /// + /// If true, the admin is voluntarily deadminned. They can re-admin at any time. + /// + public bool Deadminned { get; set; } + + /// + /// If true, the admin is suspended by an admin with PERMISSIONS. They will not have in-game permissions. + /// + public bool Suspended { get; set; } + public int? AdminRankId { get; set; } public AdminRank? AdminRank { get; set; } public List Flags { get; set; } = default!; diff --git a/Content.Server/Administration/Managers/AdminManager.cs b/Content.Server/Administration/Managers/AdminManager.cs index 2e2ebd31baa8f6..05dacdc577a320 100644 --- a/Content.Server/Administration/Managers/AdminManager.cs +++ b/Content.Server/Administration/Managers/AdminManager.cs @@ -91,14 +91,29 @@ public void DeAdmin(ICommonSession session) _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name))); _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-normal-player-message")); - var plyData = session.ContentData()!; - plyData.ExplicitlyDeadminned = true; + UpdateDatabaseDeadminnedState(session, true); reg.Data.Active = false; SendPermsChangedEvent(session); UpdateAdminStatus(session); } + private async void UpdateDatabaseDeadminnedState(ICommonSession player, bool newState) + { + try + { + // NOTE: This function gets called if you deadmin/readmin from a transient admin status. + // (e.g. loginlocal) + // In which case there may not be a database record. + // The DB function handles this scenario fine, but it's worth noting. + await _dbManager.UpdateAdminDeadminnedAsync(player.UserId, newState); + } + catch (Exception e) + { + _sawmill.Error("Failed to save deadmin state to database for {Admin}", player.UserId); + } + } + public void Stealth(ICommonSession session) { if (!_admins.TryGetValue(session, out var reg)) @@ -151,8 +166,7 @@ public void ReAdmin(ICommonSession session) _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-admin-message")); - var plyData = session.ContentData()!; - plyData.ExplicitlyDeadminned = false; + UpdateDatabaseDeadminnedState(session, false); reg.Data.Active = true; if (!reg.Data.Stealth) @@ -208,13 +222,13 @@ public async void ReloadAdmin(ICommonSession player) curAdmin.IsSpecialLogin = special; curAdmin.RankId = rankId; curAdmin.Data = aData; - } - if (!player.ContentData()!.ExplicitlyDeadminned) - { - aData.Active = true; + if (curAdmin.Data.Active) + { + aData.Active = true; - _chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message")); + _chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message")); + } } if (player.ContentData()!.Stealthed) @@ -381,10 +395,8 @@ private async void LoginAdminMaybe(ICommonSession session) if (session.ContentData()!.Stealthed) reg.Data.Stealth = true; - if (!session.ContentData()!.ExplicitlyDeadminned) + if (reg.Data.Active) { - reg.Data.Active = true; - if (_cfg.GetCVar(CCVars.AdminAnnounceLogin)) { if (reg.Data.Stealth) @@ -430,6 +442,7 @@ private async void LoginAdminMaybe(ICommonSession session) { Title = Loc.GetString("admin-manager-admin-data-host-title"), Flags = AdminFlagsHelper.Everything, + Active = true, }; return (data, null, true); @@ -444,6 +457,12 @@ private async void LoginAdminMaybe(ICommonSession session) return null; } + if (dbData.Suspended) + { + // Suspended admins don't count. + return null; + } + var flags = AdminFlags.None; if (dbData.AdminRank != null) @@ -466,7 +485,8 @@ private async void LoginAdminMaybe(ICommonSession session) var data = new AdminData { - Flags = flags + Flags = flags, + Active = !dbData.Deadminned, }; if (dbData.Title != null && _cfg.GetCVar(CCVars.AdminUseCustomNamesAdminRank)) diff --git a/Content.Server/Administration/Managers/IWatchlistWebhookManager.cs b/Content.Server/Administration/Managers/IWatchlistWebhookManager.cs new file mode 100644 index 00000000000000..6be4805365f85b --- /dev/null +++ b/Content.Server/Administration/Managers/IWatchlistWebhookManager.cs @@ -0,0 +1,23 @@ +using Content.Server.Administration.Notes; +using Content.Server.Database; +using Content.Server.Discord; +using Content.Shared.CCVar; +using Robust.Server; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using System.Linq; + +namespace Content.Server.Administration.Managers; + +/// +/// This manager sends a webhook notification whenever a player with an active +/// watchlist joins the server. +/// +public interface IWatchlistWebhookManager +{ + void Initialize(); + void Update(); +} diff --git a/Content.Server/Administration/Managers/WatchlistWebhookManager.cs b/Content.Server/Administration/Managers/WatchlistWebhookManager.cs new file mode 100644 index 00000000000000..054d45bfd0e7ce --- /dev/null +++ b/Content.Server/Administration/Managers/WatchlistWebhookManager.cs @@ -0,0 +1,143 @@ +using Content.Server.Administration.Notes; +using Content.Server.Database; +using Content.Server.Discord; +using Content.Shared.CCVar; +using Robust.Server; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Timing; +using System.Linq; +using System.Text; + +namespace Content.Server.Administration.Managers; + +/// +/// This manager sends a Discord webhook notification whenever a player with an active +/// watchlist joins the server. +/// +public sealed class WatchlistWebhookManager : IWatchlistWebhookManager +{ + [Dependency] private readonly IAdminNotesManager _adminNotes = default!; + [Dependency] private readonly IBaseServer _baseServer = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly DiscordWebhook _discord = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + private ISawmill _sawmill = default!; + + private string _webhookUrl = default!; + private TimeSpan _bufferTime; + + private List watchlistConnections = new(); + private TimeSpan? _bufferStartTime; + + public void Initialize() + { + _sawmill = Logger.GetSawmill("discord"); + _cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionBufferTime, SetBufferTime, true); + _cfg.OnValueChanged(CCVars.DiscordWatchlistConnectionWebhook, SetWebhookUrl, true); + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + } + + private void SetBufferTime(float bufferTimeSeconds) + { + _bufferTime = TimeSpan.FromSeconds(bufferTimeSeconds); + } + + private void SetWebhookUrl(string webhookUrl) + { + _webhookUrl = webhookUrl; + } + + private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus != SessionStatus.Connected) + return; + + var watchlists = await _adminNotes.GetActiveWatchlists(e.Session.UserId); + + if (watchlists.Count == 0) + return; + + watchlistConnections.Add(new WatchlistConnection(e.Session.Name, watchlists)); + + if (_bufferTime > TimeSpan.Zero) + { + if (_bufferStartTime == null) + _bufferStartTime = _gameTiming.RealTime; + } + else + { + SendDiscordMessage(); + } + } + + public void Update() + { + if (_bufferStartTime != null && _gameTiming.RealTime > (_bufferStartTime + _bufferTime)) + { + SendDiscordMessage(); + _bufferStartTime = null; + } + } + + private async void SendDiscordMessage() + { + try + { + if (string.IsNullOrWhiteSpace(_webhookUrl)) + return; + + var webhookData = await _discord.GetWebhook(_webhookUrl); + if (webhookData == null) + return; + + var webhookIdentifier = webhookData.Value.ToIdentifier(); + + var messageBuilder = new StringBuilder(Loc.GetString("discord-watchlist-connection-header", + ("players", watchlistConnections.Count), + ("serverName", _baseServer.ServerName))); + + foreach (var connection in watchlistConnections) + { + messageBuilder.Append('\n'); + + var watchlist = connection.Watchlists.First(); + var expiry = watchlist.ExpirationTime?.ToUnixTimeSeconds(); + messageBuilder.Append(Loc.GetString("discord-watchlist-connection-entry", + ("playerName", connection.PlayerName), + ("message", watchlist.Message), + ("expiry", expiry ?? 0), + ("otherWatchlists", connection.Watchlists.Count - 1))); + } + + var payload = new WebhookPayload { Content = messageBuilder.ToString() }; + + await _discord.CreateMessage(webhookIdentifier, payload); + } + catch (Exception e) + { + _sawmill.Error($"Error while sending discord watchlist connection message:\n{e}"); + } + + // Clear the buffered list regardless of whether the message is sent successfully + // This prevents infinitely buffering connections if we fail to send a message + watchlistConnections.Clear(); + } + + private sealed class WatchlistConnection + { + public string PlayerName; + public List Watchlists; + + public WatchlistConnection(string playerName, List watchlists) + { + PlayerName = playerName; + Watchlists = watchlists; + } + } +} diff --git a/Content.Server/Administration/UI/PermissionsEui.cs b/Content.Server/Administration/UI/PermissionsEui.cs index 7c2d2cbaf20a1b..7923740c29ba8b 100644 --- a/Content.Server/Administration/UI/PermissionsEui.cs +++ b/Content.Server/Administration/UI/PermissionsEui.cs @@ -76,7 +76,8 @@ public override EuiStateBase GetNewState() Title = p.a.Title, RankId = p.a.AdminRankId, UserId = new NetUserId(p.a.UserId), - UserName = p.lastUserName + UserName = p.lastUserName, + Suspended = p.a.Suspended, }).ToArray(), AdminRanks = _adminRanks.ToDictionary(a => a.Id, a => new PermissionsEuiState.AdminRankData @@ -255,6 +256,7 @@ private async Task HandleUpdateAdmin(UpdateAdmin ua) admin.Title = ua.Title; admin.AdminRankId = ua.RankId; admin.Flags = GenAdminFlagList(ua.PosFlags, ua.NegFlags); + admin.Suspended = ua.Suspended; await _db.UpdateAdminAsync(admin); @@ -335,7 +337,8 @@ private async Task HandleCreateAdmin(AddAdmin ca) Flags = GenAdminFlagList(ca.PosFlags, ca.NegFlags), AdminRankId = ca.RankId, UserId = userId.UserId, - Title = ca.Title + Title = ca.Title, + Suspended = ca.Suspended, }; await _db.AddAdminAsync(admin); diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs index 927ebf5f6f2b39..ab27553f446464 100644 --- a/Content.Server/Connection/ConnectionManager.cs +++ b/Content.Server/Connection/ConnectionManager.cs @@ -94,9 +94,16 @@ public void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration) time = newTime; } - public void Update() + public async void Update() { - _ipintel.Update(); + try + { + await _ipintel.Update(); + } + catch (Exception e) + { + _sawmill.Error("IPIntel update failed:" + e); + } } /* diff --git a/Content.Server/Connection/IPIntel/IPIntel.cs b/Content.Server/Connection/IPIntel/IPIntel.cs index 033cdb5cd12b03..51a0b74089d01e 100644 --- a/Content.Server/Connection/IPIntel/IPIntel.cs +++ b/Content.Server/Connection/IPIntel/IPIntel.cs @@ -38,6 +38,7 @@ public IPIntel(IIPIntelApi api, _sawmill = logManager.GetSawmill("ipintel"); cfg.OnValueChanged(CCVars.GameIPIntelEmail, b => _contactEmail = b, true); + cfg.OnValueChanged(CCVars.GameIPIntelEnabled, b => _enabled = b, true); cfg.OnValueChanged(CCVars.GameIPIntelRejectUnknown, b => _rejectUnknown = b, true); cfg.OnValueChanged(CCVars.GameIPIntelRejectBad, b => _rejectBad = b, true); cfg.OnValueChanged(CCVars.GameIPIntelRejectRateLimited, b => _rejectLimited = b, true); @@ -74,6 +75,7 @@ internal struct Ratelimits // CCVars private string? _contactEmail; + private bool _enabled; private bool _rejectUnknown; private bool _rejectBad; private bool _rejectLimited; @@ -273,12 +275,12 @@ private bool ShouldLiftRateLimit(in Ratelimits ratelimits, TimeSpan liftingTime) return _rejectBad ? (true, Loc.GetString("ipintel-suspicious")) : (false, string.Empty); } - public void Update() + public async Task Update() { - if (_gameTiming.RealTime >= _nextClean) + if (_enabled && _gameTiming.RealTime >= _nextClean) { _nextClean = _gameTiming.RealTime + TimeSpan.FromMinutes(_cleanupMins); - _db.CleanIPIntelCache(_cacheDays); + await _db.CleanIPIntelCache(_cacheDays); } } diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index cedf369d508008..c1a52447b799a2 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -751,6 +751,20 @@ public async Task UpdateAdminAsync(Admin admin, CancellationToken cancel) existing.Flags = admin.Flags; existing.Title = admin.Title; existing.AdminRankId = admin.AdminRankId; + existing.Deadminned = admin.Deadminned; + existing.Suspended = admin.Suspended; + + await db.DbContext.SaveChangesAsync(cancel); + } + + public async Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel) + { + await using var db = await GetDb(cancel); + + var adminRecord = db.DbContext.Admin.Where(a => a.UserId == userId); + await adminRecord.ExecuteUpdateAsync( + set => set.SetProperty(p => p.Deadminned, deadminned), + cancellationToken: cancel); await db.DbContext.SaveChangesAsync(cancel); } @@ -1774,8 +1788,11 @@ public async Task CleanIPIntelCache(TimeSpan range) { await using var db = await GetDb(); + // Calculating this here cause otherwise sqlite whines. + var cutoffTime = DateTime.UtcNow.Subtract(range); + await db.DbContext.IPIntelCache - .Where(w => DateTime.UtcNow - w.Time >= range) + .Where(w => w.Time <= cutoffTime) .ExecuteDeleteAsync(); await db.DbContext.SaveChangesAsync(); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 76e74bdb139f14..9fee2c021bd30c 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -217,6 +217,16 @@ Task AddConnectionLogAsync( Task AddAdminAsync(Admin admin, CancellationToken cancel = default); Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default); + /// + /// Update whether an admin has voluntarily deadminned. + /// + /// + /// This does nothing if the player is not an admin. + /// + /// The user ID of the admin. + /// Whether the admin is deadminned or not. + Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default); + Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default); Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default); Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default); @@ -674,6 +684,12 @@ public Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default) return RunDbCommand(() => _db.UpdateAdminAsync(admin, cancel)); } + public Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.UpdateAdminDeadminnedAsync(userId, deadminned, cancel)); + } + public Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default) { DbWriteOpsMetric.Inc(); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index a02cf5dcedd2f8..3d4ea922dc0e4e 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -47,6 +47,7 @@ public sealed class EntryPoint : GameServer private PlayTimeTrackingManager? _playTimeTracking; private IEntitySystemManager? _sysMan; private IServerDbManager? _dbManager; + private IWatchlistWebhookManager _watchlistWebhookManager = default!; private IConnectionManager? _connectionManager; /// @@ -95,6 +96,7 @@ public override void Init() _connectionManager = IoCManager.Resolve(); _sysMan = IoCManager.Resolve(); _dbManager = IoCManager.Resolve(); + _watchlistWebhookManager = IoCManager.Resolve(); logManager.GetSawmill("Storage").Level = LogLevel.Info; logManager.GetSawmill("db.ef").Level = LogLevel.Info; @@ -112,6 +114,7 @@ public override void Init() _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); + _watchlistWebhookManager.Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); } @@ -168,6 +171,7 @@ public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) case ModUpdateLevel.FramePostEngine: _updateManager.Update(); _playTimeTracking?.Update(); + _watchlistWebhookManager.Update(); _connectionManager?.Update(); break; } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index d91d59e741e4fa..777e134246994b 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -73,6 +73,7 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); } } diff --git a/Content.Server/Speech/Components/MumbleAccentComponent.cs b/Content.Server/Speech/Components/MumbleAccentComponent.cs new file mode 100644 index 00000000000000..0681ebab2f8759 --- /dev/null +++ b/Content.Server/Speech/Components/MumbleAccentComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Speech.Components; + +[RegisterComponent] +public sealed partial class MumbleAccentComponent : Component +{ + +} diff --git a/Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs b/Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs new file mode 100644 index 00000000000000..757f31ad9ea24a --- /dev/null +++ b/Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs @@ -0,0 +1,25 @@ +using Content.Server.Speech.Components; + +namespace Content.Server.Speech.EntitySystems; + +public sealed class MumbleAccentSystem : EntitySystem +{ + [Dependency] private readonly ReplacementAccentSystem _replacement = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAccentGet); + } + + public string Accentuate(string message, MumbleAccentComponent component) + { + return _replacement.ApplyReplacements(message, "mumble"); + } + + private void OnAccentGet(EntityUid uid, MumbleAccentComponent component, AccentGetEvent args) + { + args.Message = Accentuate(args.Message, component); + } +} diff --git a/Content.Shared/Administration/PermissionsEuiState.cs b/Content.Shared/Administration/PermissionsEuiState.cs index 4aa622f45e369d..77fdad5fa7aee1 100644 --- a/Content.Shared/Administration/PermissionsEuiState.cs +++ b/Content.Shared/Administration/PermissionsEuiState.cs @@ -18,6 +18,7 @@ public struct AdminData public NetUserId UserId; public string? UserName; public string? Title; + public bool Suspended; public AdminFlags PosFlags; public AdminFlags NegFlags; public int? RankId; @@ -41,6 +42,7 @@ public sealed class AddAdmin : EuiMessageBase public AdminFlags PosFlags; public AdminFlags NegFlags; public int? RankId; + public bool Suspended; } [Serializable, NetSerializable] @@ -57,6 +59,7 @@ public sealed class UpdateAdmin : EuiMessageBase public AdminFlags PosFlags; public AdminFlags NegFlags; public int? RankId; + public bool Suspended; } diff --git a/Content.Shared/CCVar/CCVars.Discord.cs b/Content.Shared/CCVar/CCVars.Discord.cs index a6c4ada7454119..6e4ef532cdc714 100644 --- a/Content.Shared/CCVar/CCVars.Discord.cs +++ b/Content.Shared/CCVar/CCVars.Discord.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Configuration; +using Robust.Shared.Configuration; namespace Content.Shared.CCVar; @@ -58,4 +58,18 @@ public sealed partial class CCVars /// public static readonly CVarDef DiscordRoundEndRoleWebhook = CVarDef.Create("discord.round_end_role", string.Empty, CVar.SERVERONLY); + + /// + /// URL of the Discord webhook which will relay watchlist connection notifications. If left empty, disables the webhook. + /// + public static readonly CVarDef DiscordWatchlistConnectionWebhook = + CVarDef.Create("discord.watchlist_connection_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); + + /// + /// How long to buffer watchlist connections for, in seconds. + /// All connections within this amount of time from the first one will be batched and sent as a single + /// Discord notification. If zero, always sends a separate notification for each connection (not recommended). + /// + public static readonly CVarDef DiscordWatchlistConnectionBufferTime = + CVarDef.Create("discord.watchlist_connection_buffer_time", 5f, CVar.SERVERONLY); } diff --git a/Content.Shared/Players/ContentPlayerData.cs b/Content.Shared/Players/ContentPlayerData.cs index 16b6ace3a1fc78..edf0228df2e3a5 100644 --- a/Content.Shared/Players/ContentPlayerData.cs +++ b/Content.Shared/Players/ContentPlayerData.cs @@ -32,12 +32,6 @@ public sealed class ContentPlayerData [ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))] public EntityUid? Mind { get; set; } - /// - /// If true, the player is an admin and they explicitly de-adminned mid-game, - /// so they should not regain admin if they reconnect. - /// - public bool ExplicitlyDeadminned { get; set; } - /// /// If true, the admin will not show up in adminwho except to admins with the flag. /// diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 97589cabf3eeb1..e48b8df30b33ac 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -695,5 +695,23 @@ Entries: id: 86 time: '2025-01-12T19:41:26.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33339 +- author: PJB3005 + changes: + - message: Deadmin status is now synchronized to database, making it persistent + across server restarts and between multiple game servers. + type: Tweak + - message: Admins can now be suspended via the permissions panel. This effectively + removes their admin status without completely deleting their record. + type: Add + id: 87 + time: '2025-01-14T23:46:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34048 +- author: Palladinium + changes: + - message: Added Discord relay notifications when a watchlisted player connects. + type: Add + id: 88 + time: '2025-01-15T00:32:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33483 Name: Admin Order: 1 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f3191f672c7f21..e92295d4410a21 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,19 +1,4 @@ Entries: -- author: qwerltaz - changes: - - message: Reduced wall closet range. It's now much easier to not close yourself - inside by accident. - type: Tweak - id: 7308 - time: '2024-09-07T23:44:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31933 -- author: Ilya246 - changes: - - message: Reagents that make you flammable no longer extinguish you. - type: Fix - id: 7309 - time: '2024-09-07T23:44:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31930 - author: LucasTheDrgn changes: - message: Restored functionality to the Industrial Reagent Grinder @@ -3938,3 +3923,17 @@ id: 7807 time: '2025-01-13T18:49:02.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/34412 +- author: Killerqu00 + changes: + - message: Pet carriers can now be crafted. + type: Add + id: 7808 + time: '2025-01-14T22:34:04.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34431 +- author: themias + changes: + - message: Fixed muzzles not working on some characters (e.g. dwarves) + type: Fix + id: 7809 + time: '2025-01-15T00:10:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34419 diff --git a/Resources/Locale/en-US/administration/ui/permissions-eui.ftl b/Resources/Locale/en-US/administration/ui/permissions-eui.ftl index 60283deb6166dc..228f40f828676d 100644 --- a/Resources/Locale/en-US/administration/ui/permissions-eui.ftl +++ b/Resources/Locale/en-US/administration/ui/permissions-eui.ftl @@ -14,6 +14,7 @@ permissions-eui-edit-admin-window-title-edit-placeholder = Custom title, leave b permissions-eui-edit-admin-window-no-rank-button = No rank permissions-eui-edit-admin-rank-window-name-edit-placeholder = Rank name permissions-eui-edit-admin-title-control-text = none +permissions-eui-edit-admin-window-suspended = Suspended? permissions-eui-edit-no-rank-text = none permissions-eui-edit-title-button = Edit permissions-eui-edit-admin-rank-button = Edit diff --git a/Resources/Locale/en-US/discord/watchlist-connections.ftl b/Resources/Locale/en-US/discord/watchlist-connections.ftl new file mode 100644 index 00000000000000..72dc971c0af38a --- /dev/null +++ b/Resources/Locale/en-US/discord/watchlist-connections.ftl @@ -0,0 +1,14 @@ +discord-watchlist-connection-header = + { $players -> + [one] {$players} player on a watchlist has + *[other] {$players} players on a watchlist have + } connected to {$serverName} + +discord-watchlist-connection-entry = - {$playerName} with message "{$message}"{ $expiry -> + [0] {""} + *[other] {" "}(expires ) + }{ $otherWatchlists -> + [0] {""} + [one] {" "}and {$otherWatchlists} other watchlist + *[other] {" "}and {$otherWatchlists} other watchlists + } diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/pride.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/pride.yml index e93c27c041ab56..d4ffe6be33d651 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/pride.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/pride.yml @@ -10,6 +10,7 @@ ClothingNeckLesbianPin: 3 ClothingNeckNonBinaryPin: 3 ClothingNeckPansexualPin: 3 + ClothingNeckOmnisexualPin: 3 ClothingNeckTransPin: 3 ClothingNeckAutismPin: 3 ClothingNeckGoldAutismPin: 3 diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index 8317f8af897fdd..5a742a20e7d167 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -319,8 +319,7 @@ unequipDelay: 3 - type: IngestionBlocker - type: AddAccentClothing - accent: ReplacementAccent - replacement: mumble + accent: MumbleAccent - type: Construction graph: Muzzle node: muzzle diff --git a/Resources/Prototypes/Entities/Clothing/Neck/pins.yml b/Resources/Prototypes/Entities/Clothing/Neck/pins.yml index a7dcf03f28c6a1..155bb9a82f3ca3 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/pins.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/pins.yml @@ -111,6 +111,17 @@ - type: Clothing equippedPrefix: pan +- type: entity + parent: ClothingNeckPinBase + id: ClothingNeckOmnisexualPin + name: omnisexual pin + description: Be omni do crime. + components: + - type: Sprite + state: omni + - type: Clothing + equippedPrefix: omni + - type: entity parent: ClothingNeckPinBase id: ClothingNeckTransPin @@ -121,7 +132,7 @@ state: trans - type: Clothing equippedPrefix: trans - + - type: entity parent: ClothingNeckPinBase id: ClothingNeckAutismPin diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index be26279365ed6b..96cf75d9351dc5 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -149,6 +149,7 @@ - id: ClothingNeckLesbianPin - id: ClothingNeckNonBinaryPin - id: ClothingNeckPansexualPin + - id: ClothingNeckOmnisexualPin - id: ClothingNeckTransPin - id: ClothingNeckAutismPin - id: ClothingNeckGoldAutismPin diff --git a/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml b/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml index 2d65f851d7d285..725db6731f6544 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml @@ -63,4 +63,9 @@ - type: ItemSlots - type: Item size: Ginormous - sprite: Objects/Storage/petcarrier.rsi \ No newline at end of file + sprite: Objects/Storage/petcarrier.rsi + - type: Construction + graph: PetCarrier + node: petCarrier + containers: + - entity_storage diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml index b4e2cecf8b1cef..8e998d602fba23 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml @@ -154,6 +154,12 @@ back: - ClothingNeckPansexualPin +- type: loadout + id: ClothingNeckOmnisexualPin + storage: + back: + - ClothingNeckOmnisexualPin + - type: loadout id: ClothingNeckTransPin storage: diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index 990c6f41bb17be..b35fcef66f7b35 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -27,6 +27,7 @@ - ClothingNeckLesbianPin - ClothingNeckNonBinaryPin - ClothingNeckPansexualPin + - ClothingNeckOmnisexualPin - ClothingNeckTransPin - ClothingNeckAutismPin - ClothingNeckGoldAutismPin diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/storage/pet_carrier.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/pet_carrier.yml new file mode 100644 index 00000000000000..670930fca9d459 --- /dev/null +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/pet_carrier.yml @@ -0,0 +1,16 @@ +- type: constructionGraph + id: PetCarrier + start: start + graph: + - node: start + edges: + - to: petCarrier + steps: + - material: Plastic + amount: 5 + doAfter: 7 + - material: MetalRod + amount: 4 + doAfter: 3 + - node: petCarrier + entity: PetCarrier diff --git a/Resources/Prototypes/Recipes/Crafting/improvised.yml b/Resources/Prototypes/Recipes/Crafting/improvised.yml index c87f4eb3d20c8e..55ffdf5176f820 100644 --- a/Resources/Prototypes/Recipes/Crafting/improvised.yml +++ b/Resources/Prototypes/Recipes/Crafting/improvised.yml @@ -227,3 +227,16 @@ icon: sprite: Objects/Tools/rolling_pin.rsi state: icon + +- type: construction + name: pet carrier + id: PetCarrier + graph: PetCarrier + startNode: start + targetNode: petCarrier + category: construction-category-misc + objectType: Item + description: Allows large animals to be carried comfortably. + icon: + sprite: Objects/Storage/petcarrier.rsi + state: icon diff --git a/Resources/Textures/Clothing/Neck/Misc/pins.rsi/meta.json b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/meta.json index 0619f962df3fcc..bb1013b21a06e8 100644 --- a/Resources/Textures/Clothing/Neck/Misc/pins.rsi/meta.json +++ b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "PixelTK leaves his mark on upstream, BackeTako made the gay, autism pins by Terraspark", + "copyright": "PixelTK leaves his mark on upstream, BackeTako made the gay, autism pins by Terraspark, omnisexual pin by juliangiebel", "size": { "x": 32, "y": 32 @@ -90,6 +90,13 @@ { "name": "trans-equipped-NECK", "directions": 4 + }, + { + "name": "omni" + }, + { + "name": "omni-equipped-NECK", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni-equipped-NECK.png b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni-equipped-NECK.png new file mode 100644 index 00000000000000..af88fc50ae0d00 Binary files /dev/null and b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni-equipped-NECK.png differ diff --git a/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni.png b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni.png new file mode 100644 index 00000000000000..fea907514486de Binary files /dev/null and b/Resources/Textures/Clothing/Neck/Misc/pins.rsi/omni.png differ diff --git a/Resources/Textures/Mobs/Demons/honkmother.rsi/meta.json b/Resources/Textures/Mobs/Demons/honkmother.rsi/meta.json index cdc51860cd8109..e56bed5206f336 100644 --- a/Resources/Textures/Mobs/Demons/honkmother.rsi/meta.json +++ b/Resources/Textures/Mobs/Demons/honkmother.rsi/meta.json @@ -6,6 +6,7 @@ }, "license": "CC-BY-SA-3.0", "copyright": "Created by Alekshhh for SS14.", + "metaAtlas": false, "states": [ { "name": "honkmother", diff --git a/Resources/Textures/Mobs/Demons/narsie.rsi/meta.json b/Resources/Textures/Mobs/Demons/narsie.rsi/meta.json index 61fcea8ab5584f..7fa472d05a94d7 100644 --- a/Resources/Textures/Mobs/Demons/narsie.rsi/meta.json +++ b/Resources/Textures/Mobs/Demons/narsie.rsi/meta.json @@ -6,6 +6,7 @@ }, "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/b44fcdedfb7c7d8425bd75b9caf71644a86375d1/icons/obj/antags/cult/narsie.dmi", + "metaAtlas": false, "states": [ { "name": "narsie", diff --git a/Resources/Textures/Mobs/Demons/ratvar.rsi/meta.json b/Resources/Textures/Mobs/Demons/ratvar.rsi/meta.json index 3d85e4a650cb12..cd399160754e63 100644 --- a/Resources/Textures/Mobs/Demons/ratvar.rsi/meta.json +++ b/Resources/Textures/Mobs/Demons/ratvar.rsi/meta.json @@ -6,6 +6,7 @@ "x": 512, "y": 512 }, + "metaAtlas": false, "states": [ { "name": "ratvar", @@ -39,19 +40,19 @@ 0.25, 0.25, 1, - + 0.3, 0.3, 0.2, 0.1, 0.05, - + 0.05, 0.05, 0.05, 0.05, 0.05, - + 0.05, 0.05, 0.05, @@ -60,4 +61,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/grassbm_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/grassbm_tabletop.rsi/meta.json index 5f3f5ea29eb052..ad851cfb0db6ec 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/grassbm_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/grassbm_tabletop.rsi/meta.json @@ -7,6 +7,7 @@ "x": 288, "y": 288 }, + "metaAtlas": false, "states": [ { "name": "tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/moonbm_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/moonbm_tabletop.rsi/meta.json index 4bd2d29ac1229e..b1b3151e714a2c 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/moonbm_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/moonbm_tabletop.rsi/meta.json @@ -7,6 +7,7 @@ "x": 288, "y": 288 }, + "metaAtlas": false, "states": [ { "name": "tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/sandbm_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/sandbm_tabletop.rsi/meta.json index 5f3f5ea29eb052..ad851cfb0db6ec 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/sandbm_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/sandbm_tabletop.rsi/meta.json @@ -7,6 +7,7 @@ "x": 288, "y": 288 }, + "metaAtlas": false, "states": [ { "name": "tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/shipbm_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/shipbm_tabletop.rsi/meta.json index cfde17c5dde188..f9fc6f47454a71 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/shipbm_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/shipbm_tabletop.rsi/meta.json @@ -7,6 +7,7 @@ "x": 543, "y": 543 }, + "metaAtlas": false, "states": [ { "name": "tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/snowbm_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/snowbm_tabletop.rsi/meta.json index ca554164865991..0b50ef920ecb40 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/snowbm_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/Battlemaps/snowbm_tabletop.rsi/meta.json @@ -7,6 +7,7 @@ "x": 288, "y": 288 }, + "metaAtlas": false, "states": [ { "name": "tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/backgammon_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/backgammon_tabletop.rsi/meta.json index 1ada42708a34b1..8df97809df7e47 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/backgammon_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/backgammon_tabletop.rsi/meta.json @@ -6,6 +6,7 @@ "x": 550, "y": 410 }, + "metaAtlas": false, "states": [ { "name": "backgammonBoard" diff --git a/Resources/Textures/Objects/Fun/Tabletop/chessboard_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/chessboard_tabletop.rsi/meta.json index f20e4c86f3ee9c..9b40a61dcb19ce 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/chessboard_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/chessboard_tabletop.rsi/meta.json @@ -6,6 +6,7 @@ "x": 274, "y": 274 }, + "metaAtlas": false, "states": [ { "name": "chessboard_tabletop" diff --git a/Resources/Textures/Objects/Fun/Tabletop/parchis_tabletop.rsi/meta.json b/Resources/Textures/Objects/Fun/Tabletop/parchis_tabletop.rsi/meta.json index 64f2d96581f5fa..42d089e944df88 100644 --- a/Resources/Textures/Objects/Fun/Tabletop/parchis_tabletop.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Tabletop/parchis_tabletop.rsi/meta.json @@ -6,6 +6,7 @@ "x": 548, "y": 548 }, + "metaAtlas": false, "states": [ { "name": "board" diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/meta.json index 265b9125e950a4..0194c4ec95d12b 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_1.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":32,"y":32},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/obj/singularity.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_1","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":32,"y":32},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/obj/singularity.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_1","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/meta.json index 44c79e79646927..a2dd4c566e2815 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_2.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":96,"y":96},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/96x96.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_2","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":96,"y":96},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/96x96.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_2","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/meta.json index 2d500fd12f3563..b27453ea03e86c 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_3.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":160,"y":160},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/160x160.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_3","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":160,"y":160},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/160x160.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_3","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/meta.json index 1313bc34c553ce..e2e360093fa429 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_4.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":224,"y":224},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/224x224.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_4","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":224,"y":224},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/224x224.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_4","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/meta.json index ac26dee9eff730..f99b30e11eee80 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_5.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":288,"y":288},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/288x288.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_5","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":288,"y":288},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/288x288.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_5","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} diff --git a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/meta.json b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/meta.json index 6f2a6d5cca81ac..83126268299275 100644 --- a/Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/meta.json +++ b/Resources/Textures/Structures/Power/Generation/Singularity/singularity_6.rsi/meta.json @@ -1 +1 @@ -{"version":1,"size":{"x":352,"y":352},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/352x352.dmi","license":"CC-BY-SA-3.0","states":[{"name":"singularity_6","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]} +{"version":1,"size":{"x":352,"y":352},"copyright":"Taken from https://github.com/vgstation-coders/vgstation13/blob/8eef5a676f66551bd0fb40d506486a6b3b2b0f1a/icons/effects/352x352.dmi","license":"CC-BY-SA-3.0","metaAtlas": false,"states":[{"name":"singularity_6","directions":1,"delays":[[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]]}]}