diff --git a/.editorconfig b/.editorconfig index f8f256f877c..bca85dd95a0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -362,7 +362,7 @@ resharper_csharp_qualified_using_at_nested_scope = false resharper_csharp_prefer_qualified_reference = false resharper_csharp_allow_alias = false -[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] +[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props,slnx}] indent_size = 2 [nuget.config] diff --git a/.github/workflows/build-docfx.yml b/.github/workflows/build-docfx.yml index 1f010b72910..dee31e9b312 100644 --- a/.github/workflows/build-docfx.yml +++ b/.github/workflows/build-docfx.yml @@ -21,7 +21,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/build-map-renderer.yml b/.github/workflows/build-map-renderer.yml index e6e6466d35d..61556747e41 100644 --- a/.github/workflows/build-map-renderer.yml +++ b/.github/workflows/build-map-renderer.yml @@ -36,7 +36,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/build-test-debug.yml b/.github/workflows/build-test-debug.yml index 6f5832d8b28..a103b08c1f6 100644 --- a/.github/workflows/build-test-debug.yml +++ b/.github/workflows/build-test-debug.yml @@ -36,7 +36,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/publish-testing.yml b/.github/workflows/publish-testing.yml index f56f9e75396..0b29a01428b 100644 --- a/.github/workflows/publish-testing.yml +++ b/.github/workflows/publish-testing.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v3.2.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Get Engine Tag run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d9ba93e60a1..506156eb959 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,7 +46,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Get Engine Tag run: | diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml index ed137a19d06..18e0882d07e 100644 --- a/.github/workflows/test-packaging.yml +++ b/.github/workflows/test-packaging.yml @@ -51,7 +51,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml index e5bbc6173fc..d65e46e7cb6 100644 --- a/.github/workflows/yaml-linter.yml +++ b/.github/workflows/yaml-linter.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4.1.0 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/.gitignore b/.gitignore index 610fbb77893..b1d777314f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# MSbuild binlog files +*.binlog + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py index 96a7bdae2ab..71cd8c13fa5 100644 --- a/BuildChecker/git_helper.py +++ b/BuildChecker/git_helper.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import List -SOLUTION_PATH = Path("..") / "SpaceStation14.sln" +SOLUTION_PATH = Path("..") / "SpaceStation14.slnx" # If this doesn't match the saved version we overwrite them all. CURRENT_HOOKS_VERSION = "2" QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet" diff --git a/Content.Benchmarks/Content.Benchmarks.csproj b/Content.Benchmarks/Content.Benchmarks.csproj index c3b60a1c69d..8d4dfa31bd6 100644 --- a/Content.Benchmarks/Content.Benchmarks.csproj +++ b/Content.Benchmarks/Content.Benchmarks.csproj @@ -1,17 +1,20 @@  - - $(TargetFramework) ..\bin\Content.Benchmarks\ - false false Exe true - 12 + false + disable + + + + + @@ -19,10 +22,12 @@ - - - - - + + + + + + + diff --git a/Content.Benchmarks/DeviceNetworkingBenchmark.cs b/Content.Benchmarks/DeviceNetworkingBenchmark.cs index bb2a22312ea..a3213e44f2d 100644 --- a/Content.Benchmarks/DeviceNetworkingBenchmark.cs +++ b/Content.Benchmarks/DeviceNetworkingBenchmark.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; @@ -10,6 +8,9 @@ using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Content.Benchmarks; diff --git a/Content.Client/Administration/UI/BanList/BanListEui.cs b/Content.Client/Administration/UI/BanList/BanListEui.cs index 2fca1dee523..00b27cd173f 100644 --- a/Content.Client/Administration/UI/BanList/BanListEui.cs +++ b/Content.Client/Administration/UI/BanList/BanListEui.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Linq; +using System.Numerics; using Content.Client.Administration.UI.BanList.Bans; using Content.Client.Administration.UI.BanList.RoleBans; using Content.Client.Eui; @@ -73,7 +74,7 @@ private static string FormatDate(DateTimeOffset date) return date.ToString("MM/dd/yyyy h:mm tt"); } - public static void SetData(IBanListLine line, SharedServerBan ban) where T : SharedServerBan + public static void SetData(IBanListLine line, SharedBan ban) where T : SharedBan { line.Reason.Text = ban.Reason; line.BanTime.Text = FormatDate(ban.BanTime); @@ -94,20 +95,20 @@ public static void SetData(IBanListLine line, SharedServerBan ban) where T line.BanningAdmin.Text = ban.BanningAdminName; } - private void OnLineIdsClicked(IBanListLine line) where T : SharedServerBan + private void OnLineIdsClicked(IBanListLine line) where T : SharedBan { _popup?.Close(); _popup = null; var ban = line.Ban; var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value)); - var ip = ban.Address == null + var ip = ban.Addresses.Length == 0 ? string.Empty - : Loc.GetString("ban-list-ip", ("ip", ban.Address.Value.address)); - var hwid = ban.HWId == null ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", ban.HWId)); - var guid = ban.UserId == null + : Loc.GetString("ban-list-ip", ("ip", string.Join(',', ban.Addresses.Select(a => a.address)))); + var hwid = ban.HWIds.Length == 0 ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", string.Join(',', ban.HWIds))); + var guid = ban.UserIds.Length == 0 ? string.Empty - : Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString())); + : Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds))); _popup = new BanListIdsPopup(id, ip, hwid, guid); diff --git a/Content.Client/Administration/UI/BanList/Bans/BanListControl.xaml.cs b/Content.Client/Administration/UI/BanList/Bans/BanListControl.xaml.cs index 431087568a1..a79fc4a137e 100644 --- a/Content.Client/Administration/UI/BanList/Bans/BanListControl.xaml.cs +++ b/Content.Client/Administration/UI/BanList/Bans/BanListControl.xaml.cs @@ -16,7 +16,7 @@ public BanListControl() RobustXamlLoader.Load(this); } - public void SetBans(List bans) + public void SetBans(List bans) { for (var i = Bans.ChildCount - 1; i >= 1; i--) { diff --git a/Content.Client/Administration/UI/BanList/Bans/BanListLine.xaml.cs b/Content.Client/Administration/UI/BanList/Bans/BanListLine.xaml.cs index 0c4e6e60d00..f1320ef7b99 100644 --- a/Content.Client/Administration/UI/BanList/Bans/BanListLine.xaml.cs +++ b/Content.Client/Administration/UI/BanList/Bans/BanListLine.xaml.cs @@ -7,13 +7,13 @@ namespace Content.Client.Administration.UI.BanList.Bans; [GenerateTypedNameReferences] -public sealed partial class BanListLine : BoxContainer, IBanListLine +public sealed partial class BanListLine : BoxContainer, IBanListLine { - public SharedServerBan Ban { get; } + public SharedBan Ban { get; } public event Action? IdsClicked; - public BanListLine(SharedServerBan ban) + public BanListLine(SharedBan ban) { RobustXamlLoader.Load(this); diff --git a/Content.Client/Administration/UI/BanList/IBanListLine.cs b/Content.Client/Administration/UI/BanList/IBanListLine.cs index 097bae15df7..565e7072184 100644 --- a/Content.Client/Administration/UI/BanList/IBanListLine.cs +++ b/Content.Client/Administration/UI/BanList/IBanListLine.cs @@ -3,7 +3,7 @@ namespace Content.Client.Administration.UI.BanList; -public interface IBanListLine where T : SharedServerBan +public interface IBanListLine where T : SharedBan { T Ban { get; } Label Reason { get; } diff --git a/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListControl.xaml.cs b/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListControl.xaml.cs index 1ea751deb7f..f217dec5e66 100644 --- a/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListControl.xaml.cs +++ b/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListControl.xaml.cs @@ -16,7 +16,7 @@ public RoleBanListControl() RobustXamlLoader.Load(this); } - public void SetRoleBans(List bans) + public void SetRoleBans(List bans) { for (var i = RoleBans.ChildCount - 1; i >= 1; i--) { diff --git a/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListLine.xaml.cs b/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListLine.xaml.cs index 4f77d662e1f..ca0d214e31e 100644 --- a/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListLine.xaml.cs +++ b/Content.Client/Administration/UI/BanList/RoleBans/RoleBanListLine.xaml.cs @@ -7,13 +7,13 @@ namespace Content.Client.Administration.UI.BanList.RoleBans; [GenerateTypedNameReferences] -public sealed partial class RoleBanListLine : BoxContainer, IBanListLine +public sealed partial class RoleBanListLine : BoxContainer, IBanListLine { - public SharedServerRoleBan Ban { get; } + public SharedBan Ban { get; } public event Action? IdsClicked; - public RoleBanListLine(SharedServerRoleBan ban) + public RoleBanListLine(SharedBan ban) { RobustXamlLoader.Load(this); @@ -21,7 +21,7 @@ public RoleBanListLine(SharedServerRoleBan ban) IdsHidden.OnPressed += IdsPressed; BanListEui.SetData(this, ban); - Role.Text = ban.Role; + Role.Text = string.Join(", ", ban.Roles ?? []); } private void IdsPressed(ButtonEventArgs buttonEventArgs) diff --git a/Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs b/Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs index ead1d8b00e5..ccbcb3e6c18 100644 --- a/Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs +++ b/Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs @@ -70,7 +70,7 @@ private void Refresh() TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); ServerLabel.Text = Note.ServerName ?? "Unknown"; - RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round; + RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds); AdminLabel.Text = Note.CreatedByName; PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h"; @@ -139,7 +139,12 @@ private string FormatBanMessage() private string FormatRoleBanMessage() { - var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} "); + var rolesText = string.Join( + ", ", + // Explicit cast here to avoid sandbox violation. + (IEnumerable?)Note.BannedRoles ?? [new BanRoleDef("what", "unknown")]); + + var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {rolesText} "); return FormatBanMessageCommon(banMessage); } diff --git a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml.cs b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml.cs index 18a50031582..e82b85acb6a 100644 --- a/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml.cs +++ b/Content.Client/Administration/UI/Notes/AdminNotesLinePopup.xaml.cs @@ -32,9 +32,9 @@ public AdminNotesLinePopup(SharedAdminNote note, string playerName, bool showDel IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id)); TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType)); SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None)); - RoundIdLabel.Text = note.Round == null + RoundIdLabel.Text = note.Rounds.Length == 0 ? Loc.GetString("admin-notes-round-id-unknown") - : Loc.GetString("admin-notes-round-id", ("id", note.Round)); + : Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds))); CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName)); CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))); EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName)); diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs index acc6aa36548..541e4884157 100644 --- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs +++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs @@ -8,7 +8,6 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using Serilog; namespace Content.Client.Cargo.UI; diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index a5e4fe71b82..049bc95c4d0 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -1,26 +1,24 @@ - - $(TargetFramework) - 12 - false false ..\bin\Content.Client\ Exe RA0032;nullable - enable - Debug;Release;Tools;DebugOpt - AnyCPU + + + + + + + + + - - - - diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 8395c4ce539..1c9932d6cf9 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -82,18 +82,21 @@ public sealed class EntryPoint : GameClient [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!; - public override void Init() + public override void PreInit() { - ClientContentIoC.Register(); + ClientContentIoC.Register(Dependencies); foreach (var callback in TestingCallbacks) { var cast = (ClientModuleTestingCallbacks) callback; cast.ClientBeforeIoC?.Invoke(); } + } - IoCManager.BuildGraph(); - IoCManager.InjectDependencies(this); + public override void Init() + { + Dependencies.BuildGraph(); + Dependencies.InjectDependencies(this); _contentLoc.Initialize(); _componentFactory.DoAutoRegistrations(); diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index c3c51093901..4a108122e2c 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -25,6 +25,7 @@ using Content.Client.Players.RateLimiting; using Content.Shared.Administration.Managers; using Content.Shared.Chat; +using Content.Shared.IoC; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.RateLimiting; @@ -32,10 +33,9 @@ namespace Content.Client.IoC { internal static class ClientContentIoC { - public static void Register() + public static void Register(IDependencyCollection collection) { - var collection = IoCManager.Instance!; - + SharedContentIoC.Register(collection); collection.Register(); collection.Register(); collection.Register(); diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs index c03014ee9fc..b3165ea02a9 100644 --- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs +++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml.cs @@ -1,6 +1,5 @@ using System.Numerics; using Content.Client.UserInterface.Controls; -using Prometheus; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; diff --git a/Content.Client/Parallax/Managers/ParallaxManager.cs b/Content.Client/Parallax/Managers/ParallaxManager.cs index bc7d7d60d68..5b35c6aa74e 100644 --- a/Content.Client/Parallax/Managers/ParallaxManager.cs +++ b/Content.Client/Parallax/Managers/ParallaxManager.cs @@ -98,10 +98,13 @@ public async Task LoadParallaxByName(string name) } else { - layers = await Task.WhenAll( + var layerTasks = new[] + { LoadParallaxLayers(parallaxPrototype.Layers, loadedLayers, cancel), LoadParallaxLayers(parallaxPrototype.LayersLQ, loadedLayers, cancel) - ); + }; + + layers = await Task.WhenAll(layerTasks); } cancel.ThrowIfCancellationRequested(); diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index e82aae21ba6..c2c3b181f5c 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -26,7 +26,8 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager [Dependency] private readonly IPrototypeManager _prototypes = default!; private readonly Dictionary _roles = new(); - private readonly List _roleBans = new(); + private readonly List> _jobBans = new(); + private readonly List> _antagBans = new(); private readonly List _jobWhitelists = new(); private ISawmill _sawmill = default!; @@ -53,16 +54,19 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e) // Reset on disconnect, just in case. _roles.Clear(); _jobWhitelists.Clear(); - _roleBans.Clear(); + _jobBans.Clear(); + _antagBans.Clear(); } } private void RxRoleBans(MsgRoleBans message) { - _sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries."); + _sawmill.Debug($"Received roleban info containing {message.JobBans.Count} job bans and {message.AntagBans.Count} antag bans."); - _roleBans.Clear(); - _roleBans.AddRange(message.Bans); + _jobBans.Clear(); + _jobBans.AddRange(message.JobBans); + _antagBans.Clear(); + _antagBans.AddRange(message.AntagBans); Updated?.Invoke(); } @@ -95,7 +99,7 @@ public bool IsAllowed(JobPrototype job, HumanoidCharacterProfile? profile, [NotN { reason = null; - if (_roleBans.Contains($"Job:{job.ID}")) + if (_jobBans.Contains(job.ID)) { reason = FormattedMessage.FromUnformatted(Loc.GetString("role-ban")); return false; diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 4d2ecffbb6e..355d3012ea4 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -368,7 +368,11 @@ protected override void Draw(DrawingHandleScreen handle) { labelName = decrypt.Revealed; } else if (decrypt.Phase == IFFDecryptPhase.Decrypting) { - labelName = string.Concat(decrypt.Revealed, CipherStart, decrypt.Cipher, CipherEnd); + labelName = string.Concat( + decrypt.Revealed, + CipherStart.ToString(), + decrypt.Cipher, + CipherEnd.ToString()); cipherName = true; unknownShuttle = true; } diff --git a/Content.Client/_Mono/Audio/AudioEchoSystem.cs b/Content.Client/_Mono/Audio/AudioEchoSystem.cs index d03cb0a21ac..a9fc0e93aac 100644 --- a/Content.Client/_Mono/Audio/AudioEchoSystem.cs +++ b/Content.Client/_Mono/Audio/AudioEchoSystem.cs @@ -42,7 +42,13 @@ public sealed class AreaEchoSystem : EntitySystem /// The directions that are raycasted to determine size for echo. /// Used relative to the grid. /// - private Angle[] _calculatedDirections = [Direction.North.ToAngle(), Direction.West.ToAngle(), Direction.South.ToAngle(), Direction.East.ToAngle()]; + private Angle[] _calculatedDirections = new[] + { + Direction.North.ToAngle(), + Direction.West.ToAngle(), + Direction.South.ToAngle(), + Direction.East.ToAngle() + }; /// /// Values for the minimum arbitrary size at which a certain audio preset @@ -135,7 +141,13 @@ public static Angle[] GetEffectiveDirections(bool highResolution) return directions; } - return [Direction.North.ToAngle(), Direction.West.ToAngle(), Direction.South.ToAngle(), Direction.East.ToAngle()]; + return new[] + { + Direction.North.ToAngle(), + Direction.West.ToAngle(), + Direction.South.ToAngle(), + Direction.East.ToAngle() + }; } /// diff --git a/Content.IntegrationTests/Content.IntegrationTests.csproj b/Content.IntegrationTests/Content.IntegrationTests.csproj index 2e922d25093..8c62806f74d 100644 --- a/Content.IntegrationTests/Content.IntegrationTests.csproj +++ b/Content.IntegrationTests/Content.IntegrationTests.csproj @@ -1,12 +1,10 @@  - - $(TargetFramework) ..\bin\Content.IntegrationTests\ - false false - 12 + disable + @@ -17,12 +15,12 @@ - - - - - + + + + + diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 43b188fd327..ce76e272126 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -1,17 +1,15 @@ -#nullable enable -using System.Collections.Generic; -using System.IO; -using System.Linq; +#nullable enable using Content.Server.GameTicking; using Content.Shared.Players; -using Robust.Shared.Configuration; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.UnitTesting; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.IO; namespace Content.IntegrationTests.Pair; diff --git a/Content.IntegrationTests/PoolManager.Prototypes.cs b/Content.IntegrationTests/PoolManager.Prototypes.cs index eb7518ea155..68416446e16 100644 --- a/Content.IntegrationTests/PoolManager.Prototypes.cs +++ b/Content.IntegrationTests/PoolManager.Prototypes.cs @@ -1,7 +1,8 @@ #nullable enable +using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; using System.Collections.Generic; using System.Reflection; -using Robust.Shared.Utility; namespace Content.IntegrationTests; diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 64aac16751c..8a9f37a94be 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -1,17 +1,9 @@ #nullable enable -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; using Content.Client.IoC; using Content.Client.Parallax.Managers; using Content.IntegrationTests.Pair; -using Content.IntegrationTests.Tests; using Content.IntegrationTests.Tests.Destructible; using Content.IntegrationTests.Tests.DeviceNetwork; -using Content.IntegrationTests.Tests.Interaction.Click; using Robust.Client; using Robust.Server; using Robust.Shared.Configuration; @@ -19,9 +11,15 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.UnitTesting; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; namespace Content.IntegrationTests; diff --git a/Content.IntegrationTests/PoolTestLogHandler.cs b/Content.IntegrationTests/PoolTestLogHandler.cs deleted file mode 100644 index 909bee9785a..00000000000 --- a/Content.IntegrationTests/PoolTestLogHandler.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.IO; -using Robust.Shared.Log; -using Robust.Shared.Timing; -using Serilog.Events; - -namespace Content.IntegrationTests; - -#nullable enable - -/// -/// Log handler intended for pooled integration tests. -/// -/// -/// -/// This class logs to two places: an NUnit -/// (so it nicely gets attributed to a test in your IDE), -/// and an in-memory ring buffer for diagnostic purposes. -/// If test pooling breaks, the ring buffer can be used to see what the broken instance has gone through. -/// -/// -/// The active test context can be swapped out so pooled instances can correctly have their logs attributed. -/// -/// -public sealed class PoolTestLogHandler : ILogHandler -{ - private readonly string? _prefix; - - private RStopwatch _stopwatch; - - public TextWriter? ActiveContext { get; private set; } - - public LogLevel? FailureLevel { get; set; } - - public PoolTestLogHandler(string? prefix) - { - _prefix = prefix != null ? $"{prefix}: " : ""; - } - - public bool ShuttingDown; - - public void Log(string sawmillName, LogEvent message) - { - var level = message.Level.ToRobust(); - - if (ShuttingDown && (FailureLevel == null || level < FailureLevel)) - return; - - if (ActiveContext is not { } testContext) - { - // If this gets hit it means something is logging to this instance while it's "between" tests. - // This is a bug in either the game or the testing system, and must always be investigated. - throw new InvalidOperationException("Log to pool test log handler without active test context"); - } - - var name = LogMessage.LogLevelToName(level); - var seconds = _stopwatch.Elapsed.TotalSeconds; - var rendered = message.RenderMessage(); - var line = $"{_prefix}{seconds:F3}s [{name}] {sawmillName}: {rendered}"; - - testContext.WriteLine(line); - - if (FailureLevel == null || level < FailureLevel) - return; - - testContext.Flush(); - Assert.Fail($"{line} Exception: {message.Exception}"); - } - - public void ClearContext() - { - ActiveContext = null; - } - - public void ActivateContext(TextWriter context) - { - _stopwatch.Restart(); - ActiveContext = context; - } -} diff --git a/Content.IntegrationTests/TestPrototypesAttribute.cs b/Content.IntegrationTests/TestPrototypesAttribute.cs deleted file mode 100644 index a6728d6728e..00000000000 --- a/Content.IntegrationTests/TestPrototypesAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.IntegrationTests; - -/// -/// Attribute that indicates that a string contains yaml prototype data that should be loaded by integration tests. -/// -[AttributeUsage(AttributeTargets.Field)] -[MeansImplicitUse] -public sealed class TestPrototypesAttribute : Attribute -{ -} diff --git a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs index b98f030b065..eb073d0c3a9 100644 --- a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs +++ b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; namespace Content.IntegrationTests.Tests.Access { diff --git a/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs b/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs index b74c35ba111..a8f7e934102 100644 --- a/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/AlarmThresholdTest.cs @@ -1,5 +1,6 @@ using Content.Shared.Atmos.Monitor; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Atmos { diff --git a/Content.IntegrationTests/Tests/Body/LegTest.cs b/Content.IntegrationTests/Tests/Body/LegTest.cs index 7b49bbe84a3..1e8eaee8b4b 100644 --- a/Content.IntegrationTests/Tests/Body/LegTest.cs +++ b/Content.IntegrationTests/Tests/Body/LegTest.cs @@ -1,10 +1,11 @@ -using System.Numerics; using Content.Server.Body.Systems; using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Rotation; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.Body { diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 1f10905708f..3d02d8e1515 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -3,16 +3,14 @@ using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Body.Components; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Configuration; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using System.Linq; -using System.Numerics; -using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.Body { @@ -50,7 +48,6 @@ public sealed class LungTest Asphyxiation: -1.5 "; - [Test, Ignore("Help me pls ;-;")] public async Task AirConsistencyTest() { // --- Setup diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs index 67163d07961..ccfa60895fd 100644 --- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; using Robust.Shared.Containers; @@ -7,6 +5,9 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Linq; namespace Content.IntegrationTests.Tests.Body; diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 205ce67a120..502d221db82 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -1,14 +1,15 @@ -using System.Numerics; using Content.Server.Body.Systems; -using Content.Shared.Buckle; using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Body.Part; +using Content.Shared.Buckle; using Content.Shared.Buckle.Components; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Standing; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.Buckle { diff --git a/Content.IntegrationTests/Tests/CargoTest.cs b/Content.IntegrationTests/Tests/CargoTest.cs index b6356e43470..11a692e0d3a 100644 --- a/Content.IntegrationTests/Tests/CargoTest.cs +++ b/Content.IntegrationTests/Tests/CargoTest.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.Nutrition.Components; using Content.Server.Nutrition.EntitySystems; @@ -13,6 +9,10 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; namespace Content.IntegrationTests.Tests; diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs index 89d33186a27..3302512c75b 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionRoundingTest.cs @@ -4,6 +4,7 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Chemistry; diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs index 6b71dd08be0..8eda581b73e 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs @@ -3,6 +3,7 @@ using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Chemistry; diff --git a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs index 6d860f0ac38..07dfd72d72e 100644 --- a/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs @@ -1,11 +1,12 @@ -using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reaction; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; using System.Linq; -using Content.Shared.Chemistry.EntitySystems; namespace Content.IntegrationTests.Tests.Chemistry { diff --git a/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs index 3fa7e64f1a4..ea1e9818679 100644 --- a/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs +++ b/Content.IntegrationTests/Tests/Commands/ForceMapTest.cs @@ -2,6 +2,7 @@ using Content.Shared.CCVar; using Robust.Shared.Configuration; using Robust.Shared.Console; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Commands; diff --git a/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs index a77761a7d17..95ac3dfef94 100644 --- a/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs +++ b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs @@ -1,9 +1,10 @@ #nullable enable -using System.Linq; using Content.Server.Objectives; using Content.Shared.Mind; using Robust.Shared.GameObjects; using Robust.Shared.Player; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Commands; @@ -54,7 +55,7 @@ await server.WaitPost(() => }); Assert.That(mindEnt, Is.Not.Null); - var mindComp = mindEnt.Value.Comp; + var mindComp = mindEnt!.Value.Comp; Assert.That(mindComp.Objectives, Is.Empty, "Dummy player started with objectives."); await pair.WaitCommand($"addobjective {playerSession.Name} {ObjectiveProtoId}"); diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs index 9e57cd4b0e6..5f77af1b104 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -32,9 +32,9 @@ public async Task PardonTest() // No bans on record Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); - Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null); + Assert.That(await sDatabase.GetBanAsync(1), Is.Null); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty); }); // Try to pardon a ban that does not exist @@ -43,9 +43,9 @@ public async Task PardonTest() // Still no bans on record Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); - Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null); + Assert.That(await sDatabase.GetBanAsync(1), Is.Null); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty); }); var banReason = "test"; @@ -57,9 +57,9 @@ public async Task PardonTest() // Should have one ban on record now Assert.Multiple(async () => { - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null); - Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null); + Assert.That(await sDatabase.GetBanAsync(1), Is.Not.Null); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); }); await pair.RunTicksSync(5); @@ -70,17 +70,17 @@ public async Task PardonTest() await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2")); // The existing ban is unaffected - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null); - var ban = await sDatabase.GetServerBanAsync(1); + var ban = await sDatabase.GetBanAsync(1); Assert.Multiple(async () => { Assert.That(ban, Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); // Check that it matches Assert.That(ban.Id, Is.EqualTo(1)); - Assert.That(ban.UserId, Is.EqualTo(clientId)); + Assert.That(ban.UserIds, Is.EquivalentTo([clientId])); Assert.That(ban.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError)); Assert.That(ban.ExpirationTime, Is.Not.Null); Assert.That(ban.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError)); @@ -95,20 +95,20 @@ public async Task PardonTest() await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1")); // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null); // Direct id lookup returns a pardoned ban - var pardonedBan = await sDatabase.GetServerBanAsync(1); + var pardonedBan = await sDatabase.GetBanAsync(1); Assert.Multiple(async () => { // Check that it matches Assert.That(pardonedBan, Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); Assert.That(pardonedBan.Id, Is.EqualTo(1)); - Assert.That(pardonedBan.UserId, Is.EqualTo(clientId)); + Assert.That(pardonedBan.UserIds, Is.EquivalentTo([clientId])); Assert.That(pardonedBan.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError)); Assert.That(pardonedBan.ExpirationTime, Is.Not.Null); Assert.That(pardonedBan.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError)); @@ -133,13 +133,13 @@ public async Task PardonTest() Assert.Multiple(async () => { // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null); + Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null); // Direct id lookup returns a pardoned ban - Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); + Assert.That(await sDatabase.GetBanAsync(1), Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1)); }); // Reconnect client. Slightly faster than dirtying the pair. diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index e4ec7e907a4..dbc3821f4d2 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -1,4 +1,3 @@ -using Content.Server.Administration.Commands; using Content.Server.Administration.Systems; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -8,6 +7,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Commands { diff --git a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs index 3d49fe490fa..09198aca9ba 100644 --- a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs +++ b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Execution; @@ -15,6 +14,8 @@ using Robust.Shared.Console; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Commands; diff --git a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs index 37c4b0c9b57..0b2bb8619a3 100644 --- a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs +++ b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs @@ -1,9 +1,9 @@ -using System.Numerics; using Content.Server.Storage.EntitySystems; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests { diff --git a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs index f610ab732e9..3fd5bae20bd 100644 --- a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs @@ -4,6 +4,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Damageable { diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs index 872e414ac35..a04ca6543e5 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs @@ -1,3 +1,5 @@ +using Robust.UnitTesting.Pool; + namespace Content.IntegrationTests.Tests.Destructible { public static class DestructibleTestPrototypes diff --git a/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs b/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs index 6ce6d5d78e0..e4447d93946 100644 --- a/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs +++ b/Content.IntegrationTests/Tests/DeviceLinking/DeviceLinkingTest.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; -using System.Linq; using Content.Server.DeviceLinking.Systems; using Content.Shared.DeviceLinking; -using Content.Shared.Prototypes; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.DeviceLinking; diff --git a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs index fdc0e1a4d4a..b38705c16a3 100644 --- a/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs +++ b/Content.IntegrationTests/Tests/DeviceNetwork/DeviceNetworkTest.cs @@ -1,10 +1,11 @@ -using System.Numerics; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Shared.DeviceNetwork; +using Content.Shared.DeviceNetwork.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Content.Shared.DeviceNetwork.Components; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.DeviceNetwork { diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index 52b669b09da..e7ddcff60e6 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -1,6 +1,4 @@ #nullable enable annotations -using System.Linq; -using System.Numerics; using Content.Server.Disposal.Unit; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -9,6 +7,9 @@ using Content.Shared.Disposal.Unit; using Robust.Shared.GameObjects; using Robust.Shared.Reflection; +using Robust.UnitTesting.Pool; +using System.Linq; +using System.Numerics; namespace Content.IntegrationTests.Tests.Disposal { diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs index 45c384f86c7..d7fb5dcb619 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs @@ -5,6 +5,7 @@ using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.DoAfter { diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs index 69fe66039b8..3806f91c840 100644 --- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -1,11 +1,11 @@ -using System.Numerics; using Content.Server.Doors.Systems; using Content.Shared.Doors.Components; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.Doors { diff --git a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs index 1afed38966f..fb4ab204cd5 100644 --- a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs @@ -5,6 +5,7 @@ using Content.Shared.Fluids; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; using System.Collections.Generic; using System.Linq; diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs index 2570e2246a6..f3bf1382b97 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs @@ -6,6 +6,7 @@ using Robust.Server.Console; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking { diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs index b9a02339fba..3f71aa3f45b 100644 --- a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -5,6 +5,7 @@ using Content.Shared.GameTicking; using Content.Shared.GameTicking.Components; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.GameRules; diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 8e22d103908..515330ead2f 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -2,6 +2,7 @@ using Content.Shared.Alert; using Content.Shared.Gravity; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Gravity { diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs index 5fdeb352c1a..348bdfccbde 100644 --- a/Content.IntegrationTests/Tests/GravityGridTest.cs +++ b/Content.IntegrationTests/Tests/GravityGridTest.cs @@ -5,6 +5,7 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Maths; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests { diff --git a/Content.IntegrationTests/Tests/Hands/HandTests.cs b/Content.IntegrationTests/Tests/Hands/HandTests.cs index d5cf75c4634..69ad0ee92b2 100644 --- a/Content.IntegrationTests/Tests/Hands/HandTests.cs +++ b/Content.IntegrationTests/Tests/Hands/HandTests.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Server.Storage.EntitySystems; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -6,6 +5,8 @@ using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Hands; diff --git a/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs b/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs index 929a2311599..4a24af546b8 100644 --- a/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs +++ b/Content.IntegrationTests/Tests/HumanInventoryUniformSlotsTest.cs @@ -1,5 +1,6 @@ using Content.Shared.Inventory; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests { diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 6ac40e92a1e..7ac838ab490 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -1,5 +1,4 @@ #nullable enable annotations -using System.Numerics; using Content.Server.Interaction; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -9,8 +8,8 @@ using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Reflection; +using Robust.UnitTesting.Pool; +using System.Numerics; namespace Content.IntegrationTests.Tests.Interaction.Click { diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index 0ed42d34764..32c091e539d 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Numerics; using Content.Client.Construction; using Content.Client.Examine; using Content.Client.Gameplay; @@ -10,9 +9,11 @@ using Content.Shared.DoAfter; using Content.Shared.Hands.Components; using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle; using Content.Shared.Mind; using Content.Shared.Players; using Robust.Client.Input; +using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Shared.GameObjects; using Robust.Shared.Log; @@ -20,9 +21,11 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Robust.Shared.Utility; using Robust.UnitTesting; -using Content.Shared.Item.ItemToggle; -using Robust.Client.State; +using Robust.UnitTesting.Pool; +using System.Numerics; +using TestMapData = Content.IntegrationTests.Pair.TestMapData; namespace Content.IntegrationTests.Tests.Interaction; @@ -37,10 +40,20 @@ namespace Content.IntegrationTests.Tests.Interaction; [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public abstract partial class InteractionTest { + /// + /// The prototype that will be spawned for the player entity at . + /// This is not a full humanoid and only has one hand by default. + /// protected virtual string PlayerPrototype => "InteractionTestMob"; + /// + /// The map path to load for the integration test. + /// If null an empty map with a single 1x1 plating grid will be generated. + /// + protected virtual ResPath? TestMapPath => null; + protected TestPair Pair = default!; - protected TestMapData MapData => Pair.TestMap!; + protected TestMapData MapData = default!; protected RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server; protected RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client; @@ -189,7 +202,7 @@ public virtual async Task Setup() CUiSys = Client.System(); // Setup map. - await Pair.CreateTestMap(); + MapData = await Pair.CreateTestMap(); PlayerCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)), MapData.MapUid)); TargetCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)), MapData.MapUid)); diff --git a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs index 639928121f6..f8038b5e634 100644 --- a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs +++ b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Systems; using Content.Server.Station.Systems; using Content.Shared.Preferences; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Internals; diff --git a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs index 39761ad0898..174557bde32 100644 --- a/Content.IntegrationTests/Tests/InventoryHelpersTest.cs +++ b/Content.IntegrationTests/Tests/InventoryHelpersTest.cs @@ -3,6 +3,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests { diff --git a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs index b75b81ab3ca..5f0c4d3d756 100644 --- a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs +++ b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; -using System.Linq; using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Reflection; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Linq; namespace Content.IntegrationTests.Tests.Linter; diff --git a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs index 4a4173eb9e4..5c6b2bc0e10 100644 --- a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs +++ b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs @@ -11,7 +11,6 @@ namespace Content.IntegrationTests.Tests.Lobby [TestOf(typeof(ServerPreferencesManager))] public sealed class CharacterCreationTest { - [Test, Ignore("Help me pls ;-;")] public async Task CreateDeleteCreateTest() { await using var pair = await PoolManager.GetServerClient(new PoolSettings { InLobby = true }); diff --git a/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs b/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs index d1d6910d9d3..03aa6923e7c 100644 --- a/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs +++ b/Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs @@ -7,7 +7,6 @@ namespace Content.IntegrationTests.Tests.Lobby; public sealed class ServerReloginTest { - [Test, Ignore("Help me pls ;-;")] public async Task Relogin() { await using var pair = await PoolManager.GetServerClient(new PoolSettings diff --git a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs index 32fcf9c1ade..55bc45b1b31 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Linq; +#nullable enable using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Shared.Ghost; @@ -7,7 +6,8 @@ using Content.Shared.Players; using Robust.Shared.Console; using Robust.Shared.GameObjects; -using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Minds; diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs index 6f331888138..513049bcad1 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs @@ -112,7 +112,7 @@ await server.WaitAssertion(() => Assert.That(entMan.EntityExists(attachedEntity), Is.True); Assert.That(attachedEntity, Is.Not.EqualTo(playerEnt)); Assert.That(entMan.HasComponent(attachedEntity)); - var transform = entMan.GetComponent(attachedEntity.Value); + var transform = entMan.GetComponent(attachedEntity!.Value); Assert.That(transform.MapID, Is.Not.EqualTo(MapId.Nullspace)); Assert.That(transform.MapID, Is.Not.EqualTo(testMap.MapId)); #pragma warning restore NUnit2045 @@ -175,7 +175,7 @@ public async Task TestOriginalDeletedWhileGhostingKeepsGhost() Assert.That(player.AttachedEntity, Is.Not.Null); Assert.That(entMan.EntityExists(player.AttachedEntity)); #pragma warning restore NUnit2045 - var originalEntity = player.AttachedEntity.Value; + var originalEntity = player.AttachedEntity!.Value; EntityUid ghost = default!; await server.WaitAssertion(() => @@ -248,7 +248,7 @@ public async Task TestGhostToAghost() var mindId = player.ContentData()?.Mind; Assert.That(mindId, Is.Not.Null); - var mind = entMan.GetComponent(mindId.Value); + var mind = entMan.GetComponent(mindId!.Value); Assert.That(mind.VisitingEntity, Is.Null); await pair.CleanReturnAsync(); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 48e11e46480..9be09a4ae9d 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Linq; +#nullable enable using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; @@ -18,6 +17,8 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Minds; diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index a448427d050..392989ecd19 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -1,7 +1,5 @@ #nullable enable -using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Power.Nodes; @@ -11,6 +9,7 @@ using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Power { diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs index 267b3637e0a..f56f3f4848f 100644 --- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; using Content.Server.Station.Systems; using Content.Shared.Inventory; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; namespace Content.IntegrationTests.Tests.Preferences; diff --git a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs index 444b3d82748..0d360584d69 100644 --- a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs @@ -1,19 +1,14 @@ -using System.Collections.Generic; -using System.Linq; using Content.Server.Database; -using Content.Shared.GameTicking; -using Content.Shared.Humanoid; using Content.Shared.Preferences; -using Content.Shared.Preferences.Loadouts; -using Content.Shared.Preferences.Loadouts.Effects; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Robust.Shared.Configuration; -using Robust.Shared.Enums; using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.UnitTesting; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Preferences { diff --git a/Content.IntegrationTests/Tests/Round/JobTest.cs b/Content.IntegrationTests/Tests/Round/JobTest.cs index befb96c7dcf..a789d9a785b 100644 --- a/Content.IntegrationTests/Tests/Round/JobTest.cs +++ b/Content.IntegrationTests/Tests/Round/JobTest.cs @@ -1,6 +1,4 @@ #nullable enable -using System.Collections.Generic; -using System.Linq; using Content.IntegrationTests.Pair; using Content.Server.GameTicking; using Content.Server.Mind; @@ -12,6 +10,9 @@ using Content.Shared.Roles.Jobs; using Robust.Shared.Network; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Linq; namespace Content.IntegrationTests.Tests.Round; diff --git a/Content.IntegrationTests/Tests/SmartFridge/SmartFridgeInteractionTest.cs b/Content.IntegrationTests/Tests/SmartFridge/SmartFridgeInteractionTest.cs index 56a3a26a7e6..d4cbcb70e04 100644 --- a/Content.IntegrationTests/Tests/SmartFridge/SmartFridgeInteractionTest.cs +++ b/Content.IntegrationTests/Tests/SmartFridge/SmartFridgeInteractionTest.cs @@ -1,5 +1,6 @@ using Content.IntegrationTests.Tests.Interaction; using Content.Shared.SmartFridge; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.SmartFridge; diff --git a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs index 78e9aa99bf2..fcd1af14130 100644 --- a/Content.IntegrationTests/Tests/Station/StationJobsTest.cs +++ b/Content.IntegrationTests/Tests/Station/StationJobsTest.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Content.IntegrationTests.Tests._NF; using Content.Server.Maps; using Content.Server.Station.Components; @@ -11,6 +9,9 @@ using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; +using System.Linq; namespace Content.IntegrationTests.Tests.Station; diff --git a/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs b/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs index 45ee69a9efa..98f748f0b45 100644 --- a/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs +++ b/Content.IntegrationTests/Tests/Storage/EntityStorageTests.cs @@ -2,6 +2,7 @@ using Content.Shared.Damage; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Storage; diff --git a/Content.IntegrationTests/Tests/StoreTests.cs b/Content.IntegrationTests/Tests/StoreTests.cs index 30f6d9d96da..12d66ba02cb 100644 --- a/Content.IntegrationTests/Tests/StoreTests.cs +++ b/Content.IntegrationTests/Tests/StoreTests.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Content.Server.Store.Systems; using Content.Server.Traitor.Uplink; using Content.Shared.FixedPoint; using Content.Shared.Inventory; @@ -10,8 +6,9 @@ using Content.Shared.Store.Components; using Content.Shared.StoreDiscount.Components; using Robust.Shared.GameObjects; -using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests; diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs index e6cd2accaf5..ed74f94ee11 100644 --- a/Content.IntegrationTests/Tests/Tag/TagTest.cs +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -1,10 +1,11 @@ #nullable enable -using System.Collections.Generic; using Content.Shared.Tag; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; namespace Content.IntegrationTests.Tests.Tag { diff --git a/Content.IntegrationTests/Tests/Toolshed/AdminTest.cs b/Content.IntegrationTests/Tests/Toolshed/AdminTest.cs index ecb11fc1ba4..62ffb39e15f 100644 --- a/Content.IntegrationTests/Tests/Toolshed/AdminTest.cs +++ b/Content.IntegrationTests/Tests/Toolshed/AdminTest.cs @@ -14,7 +14,7 @@ public async Task AllCommandsHavePermissions() var toolMan = Server.ResolveDependency(); var admin = Server.ResolveDependency(); var ignored = new HashSet() - {typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly}; + {typeof(LocTest).Assembly}; await Server.WaitAssertion(() => { diff --git a/Content.IntegrationTests/Tests/Toolshed/LocTest.cs b/Content.IntegrationTests/Tests/Toolshed/LocTest.cs index fb210eba505..849f9e55d3b 100644 --- a/Content.IntegrationTests/Tests/Toolshed/LocTest.cs +++ b/Content.IntegrationTests/Tests/Toolshed/LocTest.cs @@ -19,7 +19,7 @@ public async Task AllCommandsHaveDescriptions() var locStrings = new HashSet(); var ignored = new HashSet() - {typeof(LocTest).Assembly, typeof(Robust.UnitTesting.Shared.Toolshed.LocTest).Assembly}; + {typeof(LocTest).Assembly}; await Server.WaitAssertion(() => { diff --git a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs index d460fd354f0..735bc81b1c1 100644 --- a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs @@ -1,8 +1,9 @@ -#nullable enable +#nullable enable using Content.Shared.Physics; using Content.Shared.Spawning; using Robust.Shared.GameObjects; using Robust.Shared.Physics.Systems; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Utility { diff --git a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs index 19b25816fa3..97872ce586d 100644 --- a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs @@ -1,7 +1,7 @@ -using System.Linq; using Content.Shared.Containers.ItemSlots; using Content.Shared.Whitelist; -using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Utility { diff --git a/Content.IntegrationTests/Tests/Utility/SandboxTest.cs b/Content.IntegrationTests/Tests/Utility/SandboxTest.cs index 8ff2c126ea1..b1ad4f03076 100644 --- a/Content.IntegrationTests/Tests/Utility/SandboxTest.cs +++ b/Content.IntegrationTests/Tests/Utility/SandboxTest.cs @@ -6,6 +6,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.UnitTesting; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Utility; diff --git a/Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs b/Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs index bb4f5f42319..76ce3b5b53f 100644 --- a/Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs +++ b/Content.IntegrationTests/Tests/Vending/VendingInteractionTest.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.IntegrationTests.Tests.Interaction; using Content.Server.VendingMachines; using Content.Shared.Damage; @@ -6,6 +5,8 @@ using Content.Shared.FixedPoint; using Content.Shared.VendingMachines; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests.Vending; diff --git a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs index 55545d2cf5b..9ddb141f625 100644 --- a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs +++ b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Generic; using Content.Server.VendingMachines; using Content.Server.Wires; using Content.Shared.Cargo.Prototypes; @@ -12,6 +11,8 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.UnitTesting.Pool; +using System.Collections.Generic; namespace Content.IntegrationTests.Tests { diff --git a/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs b/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs index 920dc088186..0bad4d75263 100644 --- a/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs +++ b/Content.IntegrationTests/Tests/Wires/WireLayoutTest.cs @@ -1,9 +1,10 @@ -using Content.Server.Doors; +using Content.Server.Doors; using Content.Server.Power; using Content.Server.Wires; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.UnitTesting.Pool; namespace Content.IntegrationTests.Tests.Wires; diff --git a/Content.IntegrationTests/Tests/XenoArtifactTest.cs b/Content.IntegrationTests/Tests/XenoArtifactTest.cs index ac4c58c52cc..ee70133d062 100644 --- a/Content.IntegrationTests/Tests/XenoArtifactTest.cs +++ b/Content.IntegrationTests/Tests/XenoArtifactTest.cs @@ -1,7 +1,8 @@ -using System.Linq; using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact.Components; using Robust.Shared.GameObjects; +using Robust.UnitTesting.Pool; +using System.Linq; namespace Content.IntegrationTests.Tests; diff --git a/Content.IntegrationTests/Tests/_Lua/SPDXHunter.cs b/Content.IntegrationTests/Tests/_Lua/SPDXHunter.cs index e6b797affee..bb648e35e64 100644 --- a/Content.IntegrationTests/Tests/_Lua/SPDXHunter.cs +++ b/Content.IntegrationTests/Tests/_Lua/SPDXHunter.cs @@ -54,8 +54,12 @@ public void NoSpdxInCode() var dir = new DirectoryInfo(currentDir); while (dir != null) { - if (dir.GetFiles("*.sln").Any() || dir.GetFiles("Content.Shared.csproj", SearchOption.TopDirectoryOnly).Any()) - { return dir.FullName; } + if (dir.GetFiles("*.sln").Any() || dir.GetFiles("*.slnx").Any()) + return dir.FullName; + + var sharedProj = Path.Combine(dir.FullName, "Content.Shared", "Content.Shared.csproj"); + if (File.Exists(sharedProj)) + return dir.FullName; dir = dir.Parent; } return null; diff --git a/Content.MapRenderer/Content.MapRenderer.csproj b/Content.MapRenderer/Content.MapRenderer.csproj index 43207177322..98fb446bd56 100644 --- a/Content.MapRenderer/Content.MapRenderer.csproj +++ b/Content.MapRenderer/Content.MapRenderer.csproj @@ -3,19 +3,27 @@ Exe ..\bin\Content.MapRenderer\ false - enable true + false + + - + + + + + + + diff --git a/Content.Packaging/Content.Packaging.csproj b/Content.Packaging/Content.Packaging.csproj index 9823e40b8e3..d278b487345 100644 --- a/Content.Packaging/Content.Packaging.csproj +++ b/Content.Packaging/Content.Packaging.csproj @@ -2,13 +2,13 @@ Exe enable - enable True - - - + + + + diff --git a/Content.Packaging/DepsHandler.cs b/Content.Packaging/DepsHandler.cs new file mode 100644 index 00000000000..9907b97ba9d --- /dev/null +++ b/Content.Packaging/DepsHandler.cs @@ -0,0 +1,80 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Content.Packaging; + +/// +/// Helper class for working with .deps.json files. +/// +public sealed class DepsHandler +{ + public readonly Dictionary Libraries = new(); + + public DepsHandler(DepsData data) + { + if (data.Targets.Count != 1) + throw new Exception("Expected exactly one target"); + + var target = data.Targets.Single().Value; + + foreach (var (libNameAndVersion, libInfo) in target) + { + var split = libNameAndVersion.Split('/', 2); + + Libraries.Add(split[0], libInfo); + } + } + + public static DepsHandler Load(string depsFile) + { + using var f = File.OpenRead(depsFile); + var depsData = JsonSerializer.Deserialize(f) ?? throw new InvalidOperationException("Deps are null!"); + + return new DepsHandler(depsData); + } + + public HashSet RecursiveGetLibrariesFrom(string start) + { + var found = new HashSet(); + + RecursiveAddLibraries(start, found); + + return found; + } + + private void RecursiveAddLibraries(string start, HashSet set) + { + if (!set.Add(start)) + return; + + var lib = Libraries[start]; + if (lib.Dependencies == null) + return; + + foreach (var dep in lib.Dependencies.Keys) + { + RecursiveAddLibraries(dep, set); + } + } + + public sealed class DepsData + { + [JsonInclude, JsonPropertyName("targets")] + public required Dictionary> Targets; + } + + public sealed class LibraryInfo + { + [JsonInclude, JsonPropertyName("dependencies")] + public Dictionary? Dependencies; + + [JsonInclude, JsonPropertyName("runtime")] + public Dictionary? Runtime; + + // Paths are like lib/netstandard2.0/JetBrains.Annotations.dll + public IEnumerable GetDllNames() + { + return Runtime == null ? [] : Runtime.Keys.Select(p => p.Split('/')[^1]); + } + } +} diff --git a/Content.Packaging/ServerPackaging.cs b/Content.Packaging/ServerPackaging.cs index 947a12601c5..f872ce4dd4a 100644 --- a/Content.Packaging/ServerPackaging.cs +++ b/Content.Packaging/ServerPackaging.cs @@ -34,25 +34,9 @@ public static class ServerPackaging .Select(o => o.Rid) .ToList(); - private static readonly List ServerContentAssemblies = new() - { - "Content.Server.Database", - "Content.Server", - "Content.Shared", - "Content.Shared.Database", - }; - - private static readonly List ServerExtraAssemblies = new() - { - // Python script had Npgsql. though we want Npgsql.dll as well soooo - "Npgsql", - "Microsoft", - "Discord", - }; - private static readonly List ServerNotExtraAssemblies = new() { - "Microsoft.CodeAnalysis", + "JetBrains.Annotations", }; private static readonly HashSet BinSkipFolders = new() @@ -178,23 +162,13 @@ private static async Task WriteServerResources( var inputPassCore = graph.InputCore; var inputPassResources = graph.InputResources; - var contentAssemblies = new List(ServerContentAssemblies); // Additional assemblies that need to be copied such as EFCore. var sourcePath = Path.Combine(contentDir, "bin", "Content.Server"); - // Should this be an asset pass? - // For future archaeologists I just want audio rework to work and need the audio pass so - // just porting this as is from python. - foreach (var fullPath in Directory.EnumerateFiles(sourcePath, "*.*", SearchOption.AllDirectories)) - { - var fileName = Path.GetFileNameWithoutExtension(fullPath); + var deps = DepsHandler.Load(Path.Combine(sourcePath, "Content.Server.deps.json")); - if (!ServerNotExtraAssemblies.Any(o => fileName.StartsWith(o)) && ServerExtraAssemblies.Any(o => fileName.StartsWith(o))) - { - contentAssemblies.Add(fileName); - } - } + var contentAssemblies = GetContentAssemblyNamesToCopy(deps); await RobustSharedPackaging.DoResourceCopy( Path.Combine("RobustToolbox", "bin", "Server", @@ -222,5 +196,21 @@ await RobustSharedPackaging.WriteContentAssemblies( inputPassResources.InjectFinished(); } + // This returns both content assemblies (e.g. Content.Server.dll) and dependencies (e.g. Npgsql) + private static IEnumerable GetContentAssemblyNamesToCopy(DepsHandler deps) + { + var depsContent = deps.RecursiveGetLibrariesFrom("Content.Server").SelectMany(GetLibraryNames); + var depsRobust = deps.RecursiveGetLibrariesFrom("Robust.Server").SelectMany(GetLibraryNames); + + var depsContentExclusive = depsContent.Except(depsRobust).ToHashSet(); + + // Remove .dll suffix and apply filtering. + var names = depsContentExclusive.Select(p => p[..^4]).Where(p => !ServerNotExtraAssemblies.Any(p.StartsWith)); + + return names; + + IEnumerable GetLibraryNames(string library) => deps.Libraries[library].GetDllNames(); + } + private readonly record struct PlatformReg(string Rid, string TargetOs, bool BuildByDefault); } diff --git a/Content.PatreonParser/Content.PatreonParser.csproj b/Content.PatreonParser/Content.PatreonParser.csproj index 1724ec0cea2..bba47a062d0 100644 --- a/Content.PatreonParser/Content.PatreonParser.csproj +++ b/Content.PatreonParser/Content.PatreonParser.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable true diff --git a/Content.Replay/Content.Replay.csproj b/Content.Replay/Content.Replay.csproj index 28cfc7f6666..5008214522a 100644 --- a/Content.Replay/Content.Replay.csproj +++ b/Content.Replay/Content.Replay.csproj @@ -1,26 +1,23 @@ - $(TargetFramework) - 12 - false false ..\bin\Content.Replay\ Exe RA0032;nullable - enable + + - - - - + + + diff --git a/Content.Server.Database/Content.Server.Database.csproj b/Content.Server.Database/Content.Server.Database.csproj index d98d0642db0..22b718b3636 100644 --- a/Content.Server.Database/Content.Server.Database.csproj +++ b/Content.Server.Database/Content.Server.Database.csproj @@ -1,16 +1,13 @@ - - $(TargetFramework) - 12 - false false ..\bin\Content.Server.Database\ true - enable RA0003 + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.Designer.cs b/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.Designer.cs new file mode 100644 index 00000000000..31a03edfd09 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.Designer.cs @@ -0,0 +1,2236 @@ +// +using System; +using System.Collections.Generic; +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("20260204104024_BanRefactor")] + partial class BanRefactor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.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") + .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") + .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.Ban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + 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("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Type") + .HasColumnType("smallint") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("PK_ban"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.ToTable("ban", null, t => + { + t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_address_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_address"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_address_ban_id"); + + b.ToTable("ban_address", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_hwid_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_hwid"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_hwid_ban_id"); + + b.ToTable("ban_hwid", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_ban_player"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_player_ban_id"); + + b.HasIndex("UserId", "BanId") + .IsUnique(); + + b.ToTable("ban_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_role_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoleType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_type"); + + b.HasKey("Id") + .HasName("PK_ban_role"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_role_ban_id"); + + b.HasIndex("RoleType", "RoleId", "BanId") + .IsUnique(); + + b.ToTable("ban_role", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_ban_round"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_round_ban_id"); + + b.HasIndex("RoundId", "BanId") + .IsUnique(); + + b.ToTable("ban_round", (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.DynamicMarketEntry", b => + { + b.Property("ProtoId") + .HasColumnType("text") + .HasColumnName("protoid"); + + b.Property("BasePrice") + .HasColumnType("double precision") + .HasColumnName("baseprice"); + + b.Property("BoughtUnits") + .HasColumnType("bigint") + .HasColumnName("bought_units"); + + b.Property("LastUpdate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_update"); + + b.Property("ModPrice") + .HasColumnType("double precision") + .HasColumnName("modprice"); + + b.Property("SoldUnits") + .HasColumnType("bigint") + .HasColumnName("sold_units"); + + b.HasKey("ProtoId") + .HasName("PK_dynamic_market"); + + b.ToTable("dynamic_market", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.IPIntelCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ipintel_cache_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Score") + .HasColumnType("real") + .HasColumnName("score"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("PK_ipintel_cache"); + + b.ToTable("ipintel_cache", (string)null); + }); + + 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.PrimitiveCollection>("ConstructionFavorites") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("construction_favorites"); + + 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("BankBalance") + .HasColumnType("integer") + .HasColumnName("bank_balance"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Company") + .IsRequired() + .HasColumnType("text") + .HasColumnName("company"); + + b.Property("ERPStatus") + .HasColumnType("integer") + .HasColumnName("erpstatus"); + + 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.Property("Voice") + .IsRequired() + .HasColumnType("text") + .HasColumnName("voice"); + + 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("EntityName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("entity_name"); + + 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.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.Sponsor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("sponsor_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_date"); + + b.Property("PlannedEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("planned_end_date"); + + b.Property("PlayerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("player_name"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("role"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_sponsor"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_sponsor_player_user_id"); + + b.ToTable("sponsor", (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.Unban", 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_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban", (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.Ban", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_last_edited_by_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Addresses") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_address_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Hwids") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_hwid_ban_ban_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("BanHwidId") + .HasColumnType("integer") + .HasColumnName("ban_hwid_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .HasColumnType("integer") + .HasColumnName("hwid_type"); + + b1.HasKey("BanHwidId"); + + b1.ToTable("ban_hwid"); + + b1.WithOwner() + .HasForeignKey("BanHwidId") + .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id"); + }); + + b.Navigation("Ban"); + + b.Navigation("HWId") + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Players") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_player_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Roles") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Rounds") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_ban_ban_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_round_round_id"); + + b.Navigation("Ban"); + + b.Navigation("Round"); + }); + + 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.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_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.Sponsor", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("Sponsors") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_sponsor_player_player_user_id"); + + b.Navigation("Player"); + }); + + 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("Content.Server.Database.Unban", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.Unban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_unban_ban_ban_id"); + + b.Navigation("Ban"); + }); + + 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.Ban", b => + { + b.Navigation("Addresses"); + + b.Navigation("BanHits"); + + b.Navigation("Hwids"); + + b.Navigation("Players"); + + b.Navigation("Roles"); + + b.Navigation("Rounds"); + + b.Navigation("Unban"); + }); + + 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("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + + b.Navigation("Sponsors"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.cs b/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.cs new file mode 100644 index 00000000000..dd501198007 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.cs @@ -0,0 +1,501 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class BanRefactor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_server_ban_hit_server_ban_ban_id", + table: "server_ban_hit"); + + migrationBuilder.DropTable( + name: "server_role_unban"); + + migrationBuilder.DropTable( + name: "server_unban"); + + migrationBuilder.DropTable( + name: "server_role_ban"); + + migrationBuilder.DropTable( + name: "server_ban"); + + migrationBuilder.CreateTable( + name: "ban", + columns: table => new + { + ban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + type = table.Column(type: "smallint", nullable: false), + playtime_at_note = table.Column(type: "interval", nullable: false), + ban_time = table.Column(type: "timestamp with time zone", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + reason = table.Column(type: "text", nullable: false), + severity = table.Column(type: "integer", nullable: false), + banning_admin = table.Column(type: "uuid", nullable: true), + last_edited_by_id = table.Column(type: "uuid", nullable: true), + last_edited_at = table.Column(type: "timestamp with time zone", nullable: true), + exempt_flags = table.Column(type: "integer", nullable: false), + auto_delete = table.Column(type: "boolean", nullable: false), + hidden = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban", x => x.ban_id); + table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + table.ForeignKey( + name: "FK_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "ban_address", + columns: table => new + { + ban_address_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + address = table.Column(type: "inet", nullable: false), + ban_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_address", x => x.ban_address_id); + table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + table.ForeignKey( + name: "FK_ban_address_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_hwid", + columns: table => new + { + ban_hwid_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + hwid = table.Column(type: "bytea", nullable: false), + hwid_type = table.Column(type: "integer", nullable: false), + ban_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id); + table.ForeignKey( + name: "FK_ban_hwid_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_player", + columns: table => new + { + ban_player_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "uuid", nullable: false), + ban_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_player", x => x.ban_player_id); + table.ForeignKey( + name: "FK_ban_player_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_role", + columns: table => new + { + ban_role_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + role_type = table.Column(type: "text", nullable: false), + role_id = table.Column(type: "text", nullable: false), + ban_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_role", x => x.ban_role_id); + table.ForeignKey( + name: "FK_ban_role_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_round", + columns: table => new + { + ban_round_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(type: "integer", nullable: false), + round_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_round", x => x.ban_round_id); + table.ForeignKey( + name: "FK_ban_round_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ban_round_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "unban", + columns: table => new + { + unban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(type: "integer", nullable: false), + unbanning_admin = table.Column(type: "uuid", nullable: true), + unban_time = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_unban", x => x.unban_id); + table.ForeignKey( + name: "FK_unban_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ban_banning_admin", + table: "ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_ban_last_edited_by_id", + table: "ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_address_ban_id", + table: "ban_address", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_hwid_ban_id", + table: "ban_hwid", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_player_ban_id", + table: "ban_player", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_player_user_id_ban_id", + table: "ban_player", + columns: new[] { "user_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ban_role_ban_id", + table: "ban_role", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_role_role_type_role_id_ban_id", + table: "ban_role", + columns: new[] { "role_type", "role_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ban_round_ban_id", + table: "ban_round", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_round_round_id_ban_id", + table: "ban_round", + columns: new[] { "round_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_unban_ban_id", + table: "unban", + column: "ban_id", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_server_ban_hit_ban_ban_id", + table: "server_ban_hit", + column: "ban_id", + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_server_ban_hit_ban_ban_id", + table: "server_ban_hit"); + + migrationBuilder.DropTable( + name: "ban_address"); + + migrationBuilder.DropTable( + name: "ban_hwid"); + + migrationBuilder.DropTable( + name: "ban_player"); + + migrationBuilder.DropTable( + name: "ban_role"); + + migrationBuilder.DropTable( + name: "ban_round"); + + migrationBuilder.DropTable( + name: "unban"); + + migrationBuilder.DropTable( + name: "ban"); + + migrationBuilder.CreateTable( + name: "server_ban", + columns: table => new + { + server_ban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + banning_admin = table.Column(type: "uuid", nullable: true), + last_edited_by_id = table.Column(type: "uuid", nullable: true), + round_id = table.Column(type: "integer", nullable: true), + address = table.Column(type: "inet", nullable: true), + auto_delete = table.Column(type: "boolean", nullable: false), + ban_time = table.Column(type: "timestamp with time zone", nullable: false), + exempt_flags = table.Column(type: "integer", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + hidden = table.Column(type: "boolean", nullable: false), + last_edited_at = table.Column(type: "timestamp with time zone", nullable: true), + player_user_id = table.Column(type: "uuid", nullable: true), + playtime_at_note = table.Column(type: "interval", nullable: false), + reason = table.Column(type: "text", nullable: false), + severity = table.Column(type: "integer", nullable: false), + hwid = table.Column(type: "bytea", nullable: true), + hwid_type = table.Column(type: "integer", nullable: true, defaultValue: 0) + }, + constraints: table => + { + table.PrimaryKey("PK_server_ban", x => x.server_ban_id); + table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + table.CheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + table.ForeignKey( + name: "FK_server_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_ban_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id"); + }); + + migrationBuilder.CreateTable( + name: "server_role_ban", + columns: table => new + { + server_role_ban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + banning_admin = table.Column(type: "uuid", nullable: true), + last_edited_by_id = table.Column(type: "uuid", nullable: true), + round_id = table.Column(type: "integer", nullable: true), + address = table.Column(type: "inet", nullable: true), + ban_time = table.Column(type: "timestamp with time zone", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + hidden = table.Column(type: "boolean", nullable: false), + last_edited_at = table.Column(type: "timestamp with time zone", nullable: true), + player_user_id = table.Column(type: "uuid", nullable: true), + playtime_at_note = table.Column(type: "interval", nullable: false), + reason = table.Column(type: "text", nullable: false), + role_id = table.Column(type: "text", nullable: false), + severity = table.Column(type: "integer", nullable: false), + hwid = table.Column(type: "bytea", nullable: true), + hwid_type = table.Column(type: "integer", nullable: true, defaultValue: 0) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id); + table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + table.CheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + table.ForeignKey( + name: "FK_server_role_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_role_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_role_ban_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id"); + }); + + migrationBuilder.CreateTable( + name: "server_unban", + columns: table => new + { + unban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(type: "integer", nullable: false), + unban_time = table.Column(type: "timestamp with time zone", nullable: false), + unbanning_admin = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_server_unban", x => x.unban_id); + table.ForeignKey( + name: "FK_server_unban_server_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "server_role_unban", + columns: table => new + { + role_unban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(type: "integer", nullable: false), + unban_time = table.Column(type: "timestamp with time zone", nullable: false), + unbanning_admin = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id); + table.ForeignKey( + name: "FK_server_role_unban_server_role_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_role_ban", + principalColumn: "server_role_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_address", + table: "server_ban", + column: "address"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_banning_admin", + table: "server_ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_last_edited_by_id", + table: "server_ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_player_user_id", + table: "server_ban", + column: "player_user_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_round_id", + table: "server_ban", + column: "round_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_address", + table: "server_role_ban", + column: "address"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_banning_admin", + table: "server_role_ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_last_edited_by_id", + table: "server_role_ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_player_user_id", + table: "server_role_ban", + column: "player_user_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_round_id", + table: "server_role_ban", + column: "round_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_unban_ban_id", + table: "server_role_unban", + column: "ban_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_server_unban_ban_id", + table: "server_unban", + column: "ban_id", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_server_ban_hit_server_ban_ban_id", + table: "server_ban_hit", + column: "ban_id", + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index cf52f279ad2..7e411749911 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -21,7 +21,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("ProductVersion", "10.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -519,6 +519,221 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + 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("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Type") + .HasColumnType("smallint") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("PK_ban"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.ToTable("ban", null, t => + { + t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_address_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_address"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_address_ban_id"); + + b.ToTable("ban_address", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_hwid_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_hwid"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_hwid_ban_id"); + + b.ToTable("ban_hwid", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_ban_player"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_player_ban_id"); + + b.HasIndex("UserId", "BanId") + .IsUnique(); + + b.ToTable("ban_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_role_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoleType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_type"); + + b.HasKey("Id") + .HasName("PK_ban_role"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_role_ban_id"); + + b.HasIndex("RoleType", "RoleId", "BanId") + .IsUnique(); + + b.ToTable("ban_role", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_ban_round"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_round_ban_id"); + + b.HasIndex("RoundId", "BanId") + .IsUnique(); + + b.ToTable("ban_round", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => { b.Property("Id") @@ -1115,95 +1330,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1247,156 +1373,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); - b.ToTable("server_unban", (string)null); + b.ToTable("server_ban_hit", (string)null); }); modelBuilder.Entity("Content.Server.Database.Sponsor", b => @@ -1472,6 +1452,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("trait", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Unban", 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_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => { b.Property("Id") @@ -1756,6 +1766,123 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_last_edited_by_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Addresses") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_address_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Hwids") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_hwid_ban_ban_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("BanHwidId") + .HasColumnType("integer") + .HasColumnName("ban_hwid_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b1.Property("Type") + .HasColumnType("integer") + .HasColumnName("hwid_type"); + + b1.HasKey("BanHwidId"); + + b1.ToTable("ban_hwid"); + + b1.WithOwner() + .HasForeignKey("BanHwidId") + .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id"); + }); + + b.Navigation("Ban"); + + b.Navigation("HWId") + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Players") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_player_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Roles") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Rounds") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_ban_ban_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_round_round_id"); + + b.Navigation("Ban"); + + b.Navigation("Round"); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1912,70 +2039,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") + b.HasOne("Content.Server.Database.Ban", "Ban") .WithMany("BanHits") .HasForeignKey("BanId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + .HasConstraintName("FK_server_ban_hit_ban_ban_id"); b.HasOne("Content.Server.Database.ConnectionLog", "Connection") .WithMany("BanHits") @@ -1989,86 +2060,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.Sponsor", b => { b.HasOne("Content.Server.Database.Player", "Player") @@ -2094,6 +2085,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Unban", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.Unban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_unban_ban_ban_id"); + + b.Navigation("Ban"); + }); + modelBuilder.Entity("PlayerRound", b => { b.HasOne("Content.Server.Database.Player", null) @@ -2128,6 +2131,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Flags"); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.Navigation("Addresses"); + + b.Navigation("BanHits"); + + b.Navigation("Hwids"); + + b.Navigation("Players"); + + b.Navigation("Roles"); + + b.Navigation("Rounds"); + + b.Navigation("Unban"); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Navigation("BanHits"); @@ -2157,10 +2177,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminServerBansLastEdited"); - b.Navigation("AdminServerRoleBansCreated"); - - b.Navigation("AdminServerRoleBansLastEdited"); - b.Navigation("AdminWatchlistsCreated"); b.Navigation("AdminWatchlistsDeleted"); @@ -2211,18 +2227,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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/20260204104114_BanRefactor.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.Designer.cs new file mode 100644 index 00000000000..80946d24322 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.Designer.cs @@ -0,0 +1,2153 @@ +// +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("20260204104114_BanRefactor")] + partial class BanRefactor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.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") + .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") + .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.Ban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + 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("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("PK_ban"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.ToTable("ban", null, t => + { + t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_address_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_address"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_address_ban_id"); + + b.ToTable("ban_address", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_hwid_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_hwid"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_hwid_ban_id"); + + b.ToTable("ban_hwid", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_player_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_ban_player"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_player_ban_id"); + + b.HasIndex("UserId", "BanId") + .IsUnique(); + + b.ToTable("ban_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_role_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoleType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_type"); + + b.HasKey("Id") + .HasName("PK_ban_role"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_role_ban_id"); + + b.HasIndex("RoleType", "RoleId", "BanId") + .IsUnique(); + + b.ToTable("ban_role", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_round_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_ban_round"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_round_ban_id"); + + b.HasIndex("RoundId", "BanId") + .IsUnique(); + + b.ToTable("ban_round", (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.DynamicMarketEntry", b => + { + b.Property("ProtoId") + .HasColumnType("TEXT") + .HasColumnName("protoid"); + + b.Property("BasePrice") + .HasColumnType("REAL") + .HasColumnName("baseprice"); + + b.Property("BoughtUnits") + .HasColumnType("INTEGER") + .HasColumnName("bought_units"); + + b.Property("LastUpdate") + .HasColumnType("TEXT") + .HasColumnName("last_update"); + + b.Property("ModPrice") + .HasColumnType("REAL") + .HasColumnName("modprice"); + + b.Property("SoldUnits") + .HasColumnType("INTEGER") + .HasColumnName("sold_units"); + + b.HasKey("ProtoId") + .HasName("PK_dynamic_market"); + + b.ToTable("dynamic_market", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.IPIntelCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ipintel_cache_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Score") + .HasColumnType("REAL") + .HasColumnName("score"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.HasKey("Id") + .HasName("PK_ipintel_cache"); + + b.HasIndex("Address") + .IsUnique(); + + b.ToTable("ipintel_cache", (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.PrimitiveCollection("ConstructionFavorites") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("construction_favorites"); + + 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("BankBalance") + .HasColumnType("INTEGER") + .HasColumnName("bank_balance"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Company") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("company"); + + b.Property("ERPStatus") + .HasColumnType("INTEGER") + .HasColumnName("erpstatus"); + + 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.Property("Voice") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("voice"); + + 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("EntityName") + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("entity_name"); + + 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.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.Sponsor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("sponsor_id"); + + b.Property("EndDate") + .HasColumnType("TEXT") + .HasColumnName("end_date"); + + b.Property("PlannedEndDate") + .HasColumnType("TEXT") + .HasColumnName("planned_end_date"); + + b.Property("PlayerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("player_name"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT") + .HasColumnName("role"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_sponsor"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_sponsor_player_user_id"); + + b.ToTable("sponsor", (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.Unban", 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_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban", (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.Ban", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_last_edited_by_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Addresses") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_address_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Hwids") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_hwid_ban_ban_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("BanHwidId") + .HasColumnType("INTEGER") + .HasColumnName("ban_hwid_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("hwid_type"); + + b1.HasKey("BanHwidId"); + + b1.ToTable("ban_hwid"); + + b1.WithOwner() + .HasForeignKey("BanHwidId") + .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id"); + }); + + b.Navigation("Ban"); + + b.Navigation("HWId") + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Players") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_player_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Roles") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Rounds") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_ban_ban_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_round_round_id"); + + b.Navigation("Ban"); + + b.Navigation("Round"); + }); + + 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.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_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.Sponsor", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("Sponsors") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_sponsor_player_player_user_id"); + + b.Navigation("Player"); + }); + + 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("Content.Server.Database.Unban", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.Unban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_unban_ban_ban_id"); + + b.Navigation("Ban"); + }); + + 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.Ban", b => + { + b.Navigation("Addresses"); + + b.Navigation("BanHits"); + + b.Navigation("Hwids"); + + b.Navigation("Players"); + + b.Navigation("Roles"); + + b.Navigation("Rounds"); + + b.Navigation("Unban"); + }); + + 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("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + + b.Navigation("Sponsors"); + }); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.cs b/Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.cs new file mode 100644 index 00000000000..36106e63477 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.cs @@ -0,0 +1,496 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class BanRefactor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_server_ban_hit_server_ban_ban_id", + table: "server_ban_hit"); + + migrationBuilder.DropTable( + name: "server_role_unban"); + + migrationBuilder.DropTable( + name: "server_unban"); + + migrationBuilder.DropTable( + name: "server_role_ban"); + + migrationBuilder.DropTable( + name: "server_ban"); + + migrationBuilder.CreateTable( + name: "ban", + columns: table => new + { + ban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + type = table.Column(type: "INTEGER", nullable: false), + playtime_at_note = table.Column(type: "TEXT", nullable: false), + ban_time = table.Column(type: "TEXT", nullable: false), + expiration_time = table.Column(type: "TEXT", nullable: true), + reason = table.Column(type: "TEXT", nullable: false), + severity = table.Column(type: "INTEGER", nullable: false), + banning_admin = table.Column(type: "TEXT", nullable: true), + last_edited_by_id = table.Column(type: "TEXT", nullable: true), + last_edited_at = table.Column(type: "TEXT", nullable: true), + exempt_flags = table.Column(type: "INTEGER", nullable: false), + auto_delete = table.Column(type: "INTEGER", nullable: false), + hidden = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban", x => x.ban_id); + table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + table.ForeignKey( + name: "FK_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "ban_address", + columns: table => new + { + ban_address_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + address = table.Column(type: "TEXT", nullable: false), + ban_id = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_address", x => x.ban_address_id); + table.ForeignKey( + name: "FK_ban_address_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_hwid", + columns: table => new + { + ban_hwid_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + hwid = table.Column(type: "BLOB", nullable: false), + hwid_type = table.Column(type: "INTEGER", nullable: false), + ban_id = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id); + table.ForeignKey( + name: "FK_ban_hwid_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_player", + columns: table => new + { + ban_player_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(type: "TEXT", nullable: false), + ban_id = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_player", x => x.ban_player_id); + table.ForeignKey( + name: "FK_ban_player_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_role", + columns: table => new + { + ban_role_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + role_type = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false), + ban_id = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_role", x => x.ban_role_id); + table.ForeignKey( + name: "FK_ban_role_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ban_round", + columns: table => new + { + ban_round_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(type: "INTEGER", nullable: false), + round_id = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_round", x => x.ban_round_id); + table.ForeignKey( + name: "FK_ban_round_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ban_round_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "unban", + columns: table => new + { + unban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(type: "INTEGER", nullable: false), + unbanning_admin = table.Column(type: "TEXT", nullable: true), + unban_time = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_unban", x => x.unban_id); + table.ForeignKey( + name: "FK_unban_ban_ban_id", + column: x => x.ban_id, + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ban_banning_admin", + table: "ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_ban_last_edited_by_id", + table: "ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_address_ban_id", + table: "ban_address", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_hwid_ban_id", + table: "ban_hwid", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_player_ban_id", + table: "ban_player", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_player_user_id_ban_id", + table: "ban_player", + columns: new[] { "user_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ban_role_ban_id", + table: "ban_role", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_role_role_type_role_id_ban_id", + table: "ban_role", + columns: new[] { "role_type", "role_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ban_round_ban_id", + table: "ban_round", + column: "ban_id"); + + migrationBuilder.CreateIndex( + name: "IX_ban_round_round_id_ban_id", + table: "ban_round", + columns: new[] { "round_id", "ban_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_unban_ban_id", + table: "unban", + column: "ban_id", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_server_ban_hit_ban_ban_id", + table: "server_ban_hit", + column: "ban_id", + principalTable: "ban", + principalColumn: "ban_id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_server_ban_hit_ban_ban_id", + table: "server_ban_hit"); + + migrationBuilder.DropTable( + name: "ban_address"); + + migrationBuilder.DropTable( + name: "ban_hwid"); + + migrationBuilder.DropTable( + name: "ban_player"); + + migrationBuilder.DropTable( + name: "ban_role"); + + migrationBuilder.DropTable( + name: "ban_round"); + + migrationBuilder.DropTable( + name: "unban"); + + migrationBuilder.DropTable( + name: "ban"); + + migrationBuilder.CreateTable( + name: "server_ban", + columns: table => new + { + server_ban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + banning_admin = table.Column(type: "TEXT", nullable: true), + last_edited_by_id = table.Column(type: "TEXT", nullable: true), + round_id = table.Column(type: "INTEGER", nullable: true), + address = table.Column(type: "TEXT", nullable: true), + auto_delete = table.Column(type: "INTEGER", nullable: false), + ban_time = table.Column(type: "TEXT", nullable: false), + exempt_flags = table.Column(type: "INTEGER", nullable: false), + expiration_time = table.Column(type: "TEXT", nullable: true), + hidden = table.Column(type: "INTEGER", nullable: false), + last_edited_at = table.Column(type: "TEXT", nullable: true), + player_user_id = table.Column(type: "TEXT", nullable: true), + playtime_at_note = table.Column(type: "TEXT", nullable: false), + reason = table.Column(type: "TEXT", nullable: false), + severity = table.Column(type: "INTEGER", nullable: false), + hwid = table.Column(type: "BLOB", nullable: true), + hwid_type = table.Column(type: "INTEGER", nullable: true, defaultValue: 0) + }, + constraints: table => + { + table.PrimaryKey("PK_server_ban", x => x.server_ban_id); + table.CheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + table.ForeignKey( + name: "FK_server_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_ban_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id"); + }); + + migrationBuilder.CreateTable( + name: "server_role_ban", + columns: table => new + { + server_role_ban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + banning_admin = table.Column(type: "TEXT", nullable: true), + last_edited_by_id = table.Column(type: "TEXT", nullable: true), + round_id = table.Column(type: "INTEGER", nullable: true), + address = table.Column(type: "TEXT", nullable: true), + ban_time = table.Column(type: "TEXT", nullable: false), + expiration_time = table.Column(type: "TEXT", nullable: true), + hidden = table.Column(type: "INTEGER", nullable: false), + last_edited_at = table.Column(type: "TEXT", nullable: true), + player_user_id = table.Column(type: "TEXT", nullable: true), + playtime_at_note = table.Column(type: "TEXT", nullable: false), + reason = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false), + severity = table.Column(type: "INTEGER", nullable: false), + hwid = table.Column(type: "BLOB", nullable: true), + hwid_type = table.Column(type: "INTEGER", nullable: true, defaultValue: 0) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id); + table.CheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + table.ForeignKey( + name: "FK_server_role_ban_player_banning_admin", + column: x => x.banning_admin, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_role_ban_player_last_edited_by_id", + column: x => x.last_edited_by_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_server_role_ban_round_round_id", + column: x => x.round_id, + principalTable: "round", + principalColumn: "round_id"); + }); + + migrationBuilder.CreateTable( + name: "server_unban", + columns: table => new + { + unban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(type: "INTEGER", nullable: false), + unban_time = table.Column(type: "TEXT", nullable: false), + unbanning_admin = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_server_unban", x => x.unban_id); + table.ForeignKey( + name: "FK_server_unban_server_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "server_role_unban", + columns: table => new + { + role_unban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(type: "INTEGER", nullable: false), + unban_time = table.Column(type: "TEXT", nullable: false), + unbanning_admin = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id); + table.ForeignKey( + name: "FK_server_role_unban_server_role_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_role_ban", + principalColumn: "server_role_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_address", + table: "server_ban", + column: "address"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_banning_admin", + table: "server_ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_last_edited_by_id", + table: "server_ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_player_user_id", + table: "server_ban", + column: "player_user_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_round_id", + table: "server_ban", + column: "round_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_address", + table: "server_role_ban", + column: "address"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_banning_admin", + table: "server_role_ban", + column: "banning_admin"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_last_edited_by_id", + table: "server_role_ban", + column: "last_edited_by_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_player_user_id", + table: "server_role_ban", + column: "player_user_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban_round_id", + table: "server_role_ban", + column: "round_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_unban_ban_id", + table: "server_role_unban", + column: "ban_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_server_unban_ban_id", + table: "server_unban", + column: "ban_id", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_server_ban_hit_server_ban_ban_id", + table: "server_ban_hit", + column: "ban_id", + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index f733f69b6cc..8a9b6eaa49a 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class SqliteServerDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.1"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); modelBuilder.Entity("Content.Server.Database.Admin", b => { @@ -489,6 +489,207 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + 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("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("PK_ban"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.ToTable("ban", null, t => + { + t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_address_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_address"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_address_ban_id"); + + b.ToTable("ban_address", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_hwid_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.HasKey("Id") + .HasName("PK_ban_hwid"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_hwid_ban_id"); + + b.ToTable("ban_hwid", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_player_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_ban_player"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_player_ban_id"); + + b.HasIndex("UserId", "BanId") + .IsUnique(); + + b.ToTable("ban_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_role_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoleType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_type"); + + b.HasKey("Id") + .HasName("PK_ban_role"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_role_ban_id"); + + b.HasIndex("RoleType", "RoleId", "BanId") + .IsUnique(); + + b.ToTable("ban_role", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_round_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_ban_round"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_ban_round_ban_id"); + + b.HasIndex("RoundId", "BanId") + .IsUnique(); + + b.ToTable("ban_round", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => { b.Property("Id") @@ -1056,91 +1257,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -1182,148 +1298,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); - b.ToTable("server_unban", (string)null); + b.ToTable("server_ban_hit", (string)null); }); modelBuilder.Entity("Content.Server.Database.Sponsor", b => @@ -1395,6 +1373,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("trait", (string)null); }); + modelBuilder.Entity("Content.Server.Database.Unban", 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_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => { b.Property("Id") @@ -1677,6 +1683,123 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_ban_player_last_edited_by_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + }); + + modelBuilder.Entity("Content.Server.Database.BanAddress", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Addresses") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_address_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanHwid", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Hwids") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_hwid_ban_ban_id"); + + b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 => + { + b1.Property("BanHwidId") + .HasColumnType("INTEGER") + .HasColumnName("ban_hwid_id"); + + b1.Property("Hwid") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b1.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("hwid_type"); + + b1.HasKey("BanHwidId"); + + b1.ToTable("ban_hwid"); + + b1.WithOwner() + .HasForeignKey("BanHwidId") + .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id"); + }); + + b.Navigation("Ban"); + + b.Navigation("HWId") + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.BanPlayer", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Players") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_player_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRole", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Roles") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.BanRound", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithMany("Rounds") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_ban_ban_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_ban_round_round_round_id"); + + b.Navigation("Ban"); + + b.Navigation("Round"); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1833,70 +1956,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") + b.HasOne("Content.Server.Database.Ban", "Ban") .WithMany("BanHits") .HasForeignKey("BanId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + .HasConstraintName("FK_server_ban_hit_ban_ban_id"); b.HasOne("Content.Server.Database.ConnectionLog", "Connection") .WithMany("BanHits") @@ -1910,86 +1977,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.Sponsor", b => { b.HasOne("Content.Server.Database.Player", "Player") @@ -2015,6 +2002,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.Unban", b => + { + b.HasOne("Content.Server.Database.Ban", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.Unban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_unban_ban_ban_id"); + + b.Navigation("Ban"); + }); + modelBuilder.Entity("PlayerRound", b => { b.HasOne("Content.Server.Database.Player", null) @@ -2049,6 +2048,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Flags"); }); + modelBuilder.Entity("Content.Server.Database.Ban", b => + { + b.Navigation("Addresses"); + + b.Navigation("BanHits"); + + b.Navigation("Hwids"); + + b.Navigation("Players"); + + b.Navigation("Roles"); + + b.Navigation("Rounds"); + + b.Navigation("Unban"); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Navigation("BanHits"); @@ -2078,10 +2094,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminServerBansLastEdited"); - b.Navigation("AdminServerRoleBansCreated"); - - b.Navigation("AdminServerRoleBansLastEdited"); - b.Navigation("AdminWatchlistsCreated"); b.Navigation("AdminWatchlistsDeleted"); @@ -2132,18 +2144,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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/Model.Ban.cs b/Content.Server.Database/Model.Ban.cs new file mode 100644 index 00000000000..7d1ee2ab1a1 --- /dev/null +++ b/Content.Server.Database/Model.Ban.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using Content.Shared.Database; +using Microsoft.EntityFrameworkCore; +using NpgsqlTypes; + +// ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength + +namespace Content.Server.Database; + +// +// Contains model definitions primarily related to bans. +// + +internal static class ModelBan +{ + public static void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(b => b.CreatedBy) + .WithMany(pl => pl.AdminServerBansCreated) + .HasForeignKey(b => b.BanningAdmin) + .HasPrincipalKey(pl => pl.UserId) + .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(b => b.LastEditedBy) + .WithMany(pl => pl.AdminServerBansLastEdited) + .HasForeignKey(b => b.LastEditedById) + .HasPrincipalKey(pl => pl.UserId) + .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasIndex(bp => new { bp.UserId, bp.BanId }) + .IsUnique(); + + modelBuilder.Entity() + .OwnsOne(bp => bp.HWId) + .Property(hwid => hwid.Hwid) + .HasColumnName("hwid"); + + modelBuilder.Entity() + .HasIndex(bp => new { bp.RoleType, bp.RoleId, bp.BanId }) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(bp => new { bp.RoundId, bp.BanId }) + .IsUnique(); + + // Following indices have to be made manually by migration, due to limitations in EF Core: + // https://github.com/dotnet/efcore/issues/11336 + // https://github.com/npgsql/efcore.pg/issues/2567 + // modelBuilder.Entity() + // .HasIndex(bp => new { bp.Address, bp.BanId }) + // .IsUnique(); + // modelBuilder.Entity() + // .HasIndex(hwid => new { hwid.HWId.Type, hwid.HWId.Hwid, hwid.Hwid }) + // .IsUnique(); + // (postgres only) + // modelBuilder.Entity() + // .HasIndex(ba => ba.Address) + // .IncludeProperties(ba => ba.BanId) + // .IsUnique() + // .HasMethod("gist") + // .HasOperators("inet_ops"); + + modelBuilder.Entity() + .ToTable(t => t.HasCheckConstraint("NoExemptOnRoleBan", $"type = {(int)BanType.Server} OR exempt_flags = 0")); + } +} + +/// +/// Specifies a ban of some kind. +/// +/// +/// +/// Bans come in two types: and , +/// distinguished with . +/// +/// +/// Bans have one or more "matching data", these being , , +/// and entities. If a player's connection info matches any of these, +/// the ban's effects will apply to that player. +/// +/// +/// Bans can be set to expire after a certain point in time, or be permanent. They can also be removed manually +/// ("unbanned") by an admin, which is stored as an entity existing for this ban. +/// +/// +public sealed class Ban +{ + public int Id { get; set; } + + /// + /// Whether this is a role or server ban. + /// + public required BanType Type { get; set; } + + public TimeSpan PlaytimeAtNote { get; set; } + + /// + /// The time when the ban was applied by an administrator. + /// + public DateTime BanTime { get; set; } + + /// + /// The time the ban will expire. If null, the ban is permanent and will not expire naturally. + /// + public DateTime? ExpirationTime { get; set; } + + /// + /// The administrator-stated reason for applying the ban. + /// + public string Reason { get; set; } = null!; + + /// + /// The severity of the incident + /// + public NoteSeverity Severity { get; set; } + + /// + /// User ID of the admin that initially applied the ban. + /// + [ForeignKey(nameof(CreatedBy))] + public Guid? BanningAdmin { get; set; } + + public Player? CreatedBy { get; set; } + + /// + /// User ID of the admin that last edited the note + /// + [ForeignKey(nameof(LastEditedBy))] + public Guid? LastEditedById { get; set; } + + public Player? LastEditedBy { get; set; } + public DateTime? LastEditedAt { get; set; } + + /// + /// Optional flags that allow adding exemptions to the ban via . + /// + public ServerBanExemptFlags ExemptFlags { get; set; } + + /// + /// Whether this ban should be automatically deleted from the database when it expires. + /// + /// + /// This isn't done automatically by the game, + /// you will need to set up something like a cron job to clear this from your database, + /// using a command like this: + /// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()" + /// + public bool AutoDelete { get; set; } + + /// + /// Whether to display this ban in the admin remarks (notes) panel + /// + public bool Hidden { get; set; } + + /// + /// If present, an administrator has manually repealed this ban. + /// + public Unban? Unban { get; set; } + + public List? Rounds { get; set; } + public List? Players { get; set; } + public List? Addresses { get; set; } + public List? Hwids { get; set; } + public List? Roles { get; set; } + public List? BanHits { get; set; } +} + +/// +/// Base type for entities that specify ban matching data. +/// +public interface IBanSelector +{ + int BanId { get; } + Ban? Ban { get; } +} + +/// +/// Indicates that a ban was related to a round (e.g. placed on that round). +/// +public sealed class BanRound +{ + public int Id { get; set; } + + /// + /// The ID of the ban to which this round was relevant. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + public Ban? Ban { get; set; } + + /// + /// The ID of the round to which this ban was relevant to. + /// + [ForeignKey(nameof(Round))] + public int RoundId { get; set; } + + public Round? Round { get; set; } +} + +/// +/// Specifies a player that a matches. +/// +public sealed class BanPlayer : IBanSelector +{ + public int Id { get; set; } + + /// + /// The user ID of the banned player. + /// + public Guid UserId { get; set; } + + /// + /// The ID of the ban to which this applies. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + public Ban? Ban { get; set; } +} + +/// +/// Specifies an IP address range that a matches. +/// +public sealed class BanAddress : IBanSelector +{ + public int Id { get; set; } + + /// + /// The address range being matched. + /// + public required NpgsqlInet Address { get; set; } + + /// + /// The ID of the ban to which this applies. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + public Ban? Ban { get; set; } +} + +/// +/// Specifies a HWID that a matches. +/// +public sealed class BanHwid : IBanSelector +{ + public int Id { get; set; } + + /// + /// The HWID being matched. + /// + public required TypedHwid HWId { get; set; } + + /// + /// The ID of the ban to which this applies. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + public Ban? Ban { get; set; } +} + +/// +/// A single role banned among a greater role ban record. +/// +/// +/// s of type should have one or more s +/// to store which roles are actually banned. +/// It is invalid for bans to have entities. +/// +public sealed class BanRole +{ + public int Id { get; set; } + + /// + /// What type of role is being banned. For example Job or Antag. + /// + public required string RoleType { get; set; } + + /// + /// The ID of the role being banned. This is probably something like a prototype. + /// + public required string RoleId { get; set; } + + /// + /// The ID of the ban to which this applies. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + public Ban? Ban { get; set; } +} + +/// +/// An explicit repeal of a by an administrator. +/// Having an entry for a ban neutralizes it. +/// +public sealed class Unban +{ + public int Id { get; set; } + + /// + /// The ID of ban that is being repealed. + /// + [ForeignKey(nameof(Ban))] + public int BanId { get; set; } + + /// + /// The ban that is being repealed. + /// + public Ban? Ban { get; set; } + + /// + /// The admin that repealed the ban. + /// + public Guid? UnbanningAdmin { get; set; } + + /// + /// The time the ban was repealed. + /// + public DateTime UnbanTime { get; set; } +} diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index ace1fff782f..df9704a7658 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -9,7 +9,6 @@ using System.Text.Json; using Content.Shared.Database; using Microsoft.EntityFrameworkCore; -using NpgsqlTypes; namespace Content.Server.Database { @@ -31,13 +30,17 @@ protected ServerDbContext(DbContextOptions options) : base(options) public DbSet AdminLogPlayer { get; set; } = null!; public DbSet Whitelist { get; set; } = null!; public DbSet Blacklist { get; set; } = null!; - public DbSet Ban { get; set; } = default!; - public DbSet Unban { get; set; } = default!; + public DbSet Ban { get; set; } = default!; + public DbSet BanRound { get; set; } = default!; + public DbSet BanPlayer { get; set; } = default!; + public DbSet BanAddress { get; set; } = default!; + public DbSet BanHwid { get; set; } = default!; + public DbSet BanRole { get; set; } = default!; + public DbSet Unban { get; set; } = default!; public DbSet BanExemption { get; set; } = default!; public DbSet ConnectionLog { get; set; } = default!; public DbSet ServerBanHit { get; set; } = default!; - public DbSet RoleBan { get; set; } = default!; - public DbSet RoleUnban { get; set; } = default!; + public DbSet PlayTime { get; set; } = default!; public DbSet UploadedResourceLog { get; set; } = default!; public DbSet AdminNotes { get; set; } = null!; @@ -147,43 +150,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId}); - modelBuilder.Entity() - .HasIndex(p => p.PlayerUserId); - - modelBuilder.Entity() - .HasIndex(p => p.Address); - - modelBuilder.Entity() - .HasIndex(p => p.PlayerUserId); - - modelBuilder.Entity() - .HasIndex(p => p.BanId) - .IsUnique(); - - modelBuilder.Entity().ToTable(t => - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL")); - // Ban exemption can't have flags 0 since that wouldn't exempt anything. // The row should be removed if setting to 0. modelBuilder.Entity().ToTable(t => t.HasCheckConstraint("FlagsNotZero", "flags != 0")); - modelBuilder.Entity() - .HasIndex(p => p.PlayerUserId); - - modelBuilder.Entity() - .HasIndex(p => p.Address); - - modelBuilder.Entity() - .HasIndex(p => p.PlayerUserId); - - modelBuilder.Entity() - .HasIndex(p => p.BanId) - .IsUnique(); - - modelBuilder.Entity().ToTable(t => - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL")); - modelBuilder.Entity() .HasIndex(p => p.UserId) .IsUnique(); @@ -298,34 +269,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen")); - modelBuilder.Entity() - .HasOne(ban => ban.CreatedBy) - .WithMany(author => author.AdminServerBansCreated) - .HasForeignKey(ban => ban.BanningAdmin) - .HasPrincipalKey(author => author.UserId) - .OnDelete(DeleteBehavior.SetNull); - - modelBuilder.Entity() - .HasOne(ban => ban.LastEditedBy) - .WithMany(author => author.AdminServerBansLastEdited) - .HasForeignKey(ban => ban.LastEditedById) - .HasPrincipalKey(author => author.UserId) - .OnDelete(DeleteBehavior.SetNull); - - modelBuilder.Entity() - .HasOne(ban => ban.CreatedBy) - .WithMany(author => author.AdminServerRoleBansCreated) - .HasForeignKey(ban => ban.BanningAdmin) - .HasPrincipalKey(author => author.UserId) - .OnDelete(DeleteBehavior.SetNull); - - modelBuilder.Entity() - .HasOne(ban => ban.LastEditedBy) - .WithMany(author => author.AdminServerRoleBansLastEdited) - .HasForeignKey(ban => ban.LastEditedById) - .HasPrincipalKey(author => author.UserId) - .OnDelete(DeleteBehavior.SetNull); - modelBuilder.Entity() .HasOne(w => w.Player) .WithMany(p => p.JobWhitelists) @@ -351,26 +294,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(p => p.Type) .HasDefaultValue(HwidType.Legacy); - modelBuilder.Entity() - .OwnsOne(p => p.HWId) - .Property(p => p.Hwid) - .HasColumnName("hwid"); - - modelBuilder.Entity() - .OwnsOne(p => p.HWId) - .Property(p => p.Type) - .HasDefaultValue(HwidType.Legacy); - - modelBuilder.Entity() - .OwnsOne(p => p.HWId) - .Property(p => p.Hwid) - .HasColumnName("hwid"); - - modelBuilder.Entity() - .OwnsOne(p => p.HWId) - .Property(p => p.Type) - .HasDefaultValue(HwidType.Legacy); - modelBuilder.Entity() .OwnsOne(p => p.HWId) .Property(p => p.Hwid) @@ -380,6 +303,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OwnsOne(p => p.HWId) .Property(p => p.Type) .HasDefaultValue(HwidType.Legacy); + + ModelBan.OnModelCreating(modelBuilder); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -604,10 +529,8 @@ public class Player public List AdminMessagesCreated { get; set; } = null!; public List AdminMessagesLastEdited { get; set; } = null!; public List AdminMessagesDeleted { get; set; } = null!; - public List AdminServerBansCreated { get; set; } = null!; - public List AdminServerBansLastEdited { get; set; } = null!; - public List AdminServerRoleBansCreated { get; set; } = null!; - public List AdminServerRoleBansLastEdited { get; set; } = null!; + public List AdminServerBansCreated { get; set; } = null!; + public List AdminServerBansLastEdited { get; set; } = null!; public List JobWhitelists { get; set; } = null!; public List Sponsors { get; set; } = null!; } @@ -738,30 +661,6 @@ public class AdminLogPlayer [ForeignKey("RoundId,LogId")] public AdminLog Log { get; set; } = default!; } - // Used by SS14.Admin - public interface IBanCommon where TUnban : IUnbanCommon - { - int Id { get; set; } - Guid? PlayerUserId { get; set; } - NpgsqlInet? Address { get; set; } - TypedHwid? HWId { get; set; } - DateTime BanTime { get; set; } - DateTime? ExpirationTime { get; set; } - string Reason { get; set; } - NoteSeverity Severity { get; set; } - Guid? BanningAdmin { get; set; } - TUnban? Unban { get; set; } - } - - // Used by SS14.Admin - public interface IUnbanCommon - { - int Id { get; set; } - int BanId { get; set; } - Guid? UnbanningAdmin { get; set; } - DateTime UnbanTime { get; set; } - } - /// /// Flags for use with . /// @@ -799,138 +698,6 @@ public enum ServerBanExemptFlags // @formatter:on } - /// - /// A ban from playing on the server. - /// If an incoming connection matches any of UserID, IP, or HWID, they will be blocked from joining the server. - /// - /// - /// At least one of UserID, IP, or HWID must be given (otherwise the ban would match nothing). - /// - [Table("server_ban"), Index(nameof(PlayerUserId))] - public class ServerBan : IBanCommon - { - public int Id { get; set; } - - [ForeignKey("Round")] - public int? RoundId { get; set; } - public Round? Round { get; set; } - - /// - /// The user ID of the banned player. - /// - public Guid? PlayerUserId { get; set; } - [Required] public TimeSpan PlaytimeAtNote { get; set; } - - /// - /// CIDR IP address range of the ban. The whole range can match the ban. - /// - public NpgsqlInet? Address { get; set; } - - /// - /// Hardware ID of the banned player. - /// - public TypedHwid? HWId { get; set; } - - /// - /// The time when the ban was applied by an administrator. - /// - public DateTime BanTime { get; set; } - - /// - /// The time the ban will expire. If null, the ban is permanent and will not expire naturally. - /// - public DateTime? ExpirationTime { get; set; } - - /// - /// The administrator-stated reason for applying the ban. - /// - public string Reason { get; set; } = null!; - - /// - /// The severity of the incident - /// - public NoteSeverity Severity { get; set; } - - /// - /// User ID of the admin that applied the ban. - /// - [ForeignKey("CreatedBy")] - public Guid? BanningAdmin { get; set; } - - public Player? CreatedBy { get; set; } - - /// - /// User ID of the admin that last edited the note - /// - [ForeignKey("LastEditedBy")] - public Guid? LastEditedById { get; set; } - - public Player? LastEditedBy { get; set; } - - /// - /// When the ban was last edited - /// - public DateTime? LastEditedAt { get; set; } - - /// - /// Optional flags that allow adding exemptions to the ban via . - /// - public ServerBanExemptFlags ExemptFlags { get; set; } - - /// - /// If present, an administrator has manually repealed this ban. - /// - public ServerUnban? Unban { get; set; } - - /// - /// Whether this ban should be automatically deleted from the database when it expires. - /// - /// - /// This isn't done automatically by the game, - /// you will need to set up something like a cron job to clear this from your database, - /// using a command like this: - /// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()" - /// - public bool AutoDelete { get; set; } - - /// - /// Whether to display this ban in the admin remarks (notes) panel - /// - public bool Hidden { get; set; } - - public List BanHits { get; set; } = null!; - } - - /// - /// An explicit repeal of a by an administrator. - /// Having an entry for a ban neutralizes it. - /// - [Table("server_unban")] - public class ServerUnban : IUnbanCommon - { - [Column("unban_id")] public int Id { get; set; } - - /// - /// The ID of ban that is being repealed. - /// - public int BanId { get; set; } - - /// - /// The ban that is being repealed. - /// - public ServerBan Ban { get; set; } = null!; - - /// - /// The admin that repealed the ban. - /// - public Guid? UnbanningAdmin { get; set; } - - /// - /// The time the ban repealed. - /// - public DateTime UnbanTime { get; set; } - } - /// /// An exemption for a specific user to a certain type of . /// @@ -951,7 +718,7 @@ public sealed class ServerBanExemption /// /// The ban flags to exempt this player from. - /// If any bit overlaps , the ban is ignored. + /// If any bit overlaps , the ban is ignored. /// public ServerBanExemptFlags Flags { get; set; } } @@ -1015,54 +782,10 @@ public class ServerBanHit public int BanId { get; set; } public int ConnectionId { get; set; } - public ServerBan Ban { get; set; } = null!; + public Ban Ban { get; set; } = null!; public ConnectionLog Connection { get; set; } = null!; } - [Table("server_role_ban"), Index(nameof(PlayerUserId))] - public sealed class ServerRoleBan : IBanCommon - { - public int Id { get; set; } - public int? RoundId { get; set; } - public Round? Round { get; set; } - public Guid? PlayerUserId { get; set; } - [Required] public TimeSpan PlaytimeAtNote { get; set; } - public NpgsqlInet? Address { get; set; } - public TypedHwid? HWId { get; set; } - - public DateTime BanTime { get; set; } - - public DateTime? ExpirationTime { get; set; } - - public string Reason { get; set; } = null!; - - public NoteSeverity Severity { get; set; } - [ForeignKey("CreatedBy")] public Guid? BanningAdmin { get; set; } - public Player? CreatedBy { get; set; } - - [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; } - public Player? LastEditedBy { get; set; } - public DateTime? LastEditedAt { get; set; } - - public ServerRoleUnban? Unban { get; set; } - public bool Hidden { get; set; } - - public string RoleId { get; set; } = null!; - } - - [Table("server_role_unban")] - public sealed class ServerRoleUnban : IUnbanCommon - { - [Column("role_unban_id")] public int Id { get; set; } - - public int BanId { get; set; } - public ServerRoleBan Ban { get; set; } = null!; - - public Guid? UnbanningAdmin { get; set; } - - public DateTime UnbanTime { get; set; } - } - [Table("play_time")] public sealed class PlayTime { @@ -1283,31 +1006,31 @@ public sealed class BanTemplate /// /// The reason for the ban. /// - /// + /// public string Reason { get; set; } = ""; /// /// Exemptions granted to the ban. /// - /// + /// public ServerBanExemptFlags ExemptFlags { get; set; } /// /// Severity of the ban /// - /// + /// public NoteSeverity Severity { get; set; } /// /// Ban will be automatically deleted once expired. /// - /// + /// public bool AutoDelete { get; set; } /// /// Ban is not visible to players in the remarks menu. /// - /// + /// public bool Hidden { get; set; } } diff --git a/Content.Server.Database/ModelPostgres.cs b/Content.Server.Database/ModelPostgres.cs index 7499d0b0f59..c3f41b7058e 100644 --- a/Content.Server.Database/ModelPostgres.cs +++ b/Content.Server.Database/ModelPostgres.cs @@ -39,10 +39,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // ReSharper disable StringLiteralTypo // Enforce that an address cannot be IPv6-mapped IPv4. // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. - modelBuilder.Entity().ToTable(t => - t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address")); - - modelBuilder.Entity().ToTable( t => + modelBuilder.Entity().ToTable(t => t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address")); modelBuilder.Entity().ToTable(t => diff --git a/Content.Server.Database/ModelSqlite.cs b/Content.Server.Database/ModelSqlite.cs index 1ce9465847f..8ace8c24b7f 100644 --- a/Content.Server.Database/ModelSqlite.cs +++ b/Content.Server.Database/ModelSqlite.cs @@ -55,13 +55,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) ); modelBuilder - .Entity() - .Property(e => e.Address) - .HasColumnType("TEXT") - .HasConversion(ipMaskConverter); - - modelBuilder - .Entity() + .Entity() .Property(e => e.Address) .HasColumnType("TEXT") .HasConversion(ipMaskConverter); diff --git a/Content.Server/Administration/BanList/BanListEui.cs b/Content.Server/Administration/BanList/BanListEui.cs index 2ca126bf164..549a14f6733 100644 --- a/Content.Server/Administration/BanList/BanListEui.cs +++ b/Content.Server/Administration/BanList/BanListEui.cs @@ -1,9 +1,12 @@ -using System.Threading.Tasks; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; using Content.Server.Administration.Managers; using Content.Server.Database; using Content.Server.EUI; using Content.Shared.Administration; using Content.Shared.Administration.BanList; +using Content.Shared.Database; using Content.Shared.Eui; using Robust.Shared.Network; @@ -22,8 +25,8 @@ public BanListEui() private Guid BanListPlayer { get; set; } private string BanListPlayerName { get; set; } = string.Empty; - private List Bans { get; } = new(); - private List RoleBans { get; } = new(); + private List Bans { get; } = new(); + private List RoleBans { get; } = new(); public override void Opened() { @@ -54,74 +57,38 @@ private void OnPermsChanged(AdminPermsChangedEventArgs args) private async Task LoadBans(NetUserId userId) { - foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null)) - { - SharedServerUnban? unban = null; - if (ban.Unban is { } unbanDef) - { - var unbanningAdmin = unbanDef.UnbanningAdmin == null - ? null - : (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username; - unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime); - } - - (string, int cidrMask)? ip = ("*Hidden*", 0); - var hwid = "*Hidden*"; - - if (_admins.HasAdminFlag(Player, AdminFlags.Pii)) - { - ip = ban.Address is { } address - ? (address.address.ToString(), address.cidrMask) - : null; - - hwid = ban.HWId?.ToString(); - } - - Bans.Add(new SharedServerBan( - ban.Id, - ban.UserId, - ip, - hwid, - ban.BanTime.UtcDateTime, - ban.ExpirationTime?.UtcDateTime, - ban.Reason, - ban.BanningAdmin == null - ? null - : (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username, - unban - )); - } + await LoadBansCore(userId, BanType.Server, Bans); + await LoadBansCore(userId, BanType.Role, RoleBans); } - private async Task LoadRoleBans(NetUserId userId) + private async Task LoadBansCore(NetUserId userId, BanType banType, List list) { - foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null)) + foreach (var ban in await _db.GetBansAsync(null, userId, null, null, type: banType)) { - SharedServerUnban? unban = null; + SharedUnban? unban = null; if (ban.Unban is { } unbanDef) { var unbanningAdmin = unbanDef.UnbanningAdmin == null ? null : (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username; - unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime); + unban = new SharedUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime); } - (string, int cidrMask)? ip = ("*Hidden*", 0); - var hwid = "*Hidden*"; + ImmutableArray<(string, int cidrMask)> ips = [("*Hidden*", 0)]; + ImmutableArray hwids = ["*Hidden*"]; if (_admins.HasAdminFlag(Player, AdminFlags.Pii)) { - ip = ban.Address is { } address - ? (address.address.ToString(), address.cidrMask) - : null; - - hwid = ban.HWId?.ToString(); + ips = [..ban.Addresses.Select(a => (a.address.ToString(), a.cidrMask))]; + hwids = [..ban.HWIds.Select(h => h.ToString())]; } - RoleBans.Add(new SharedServerRoleBan( + + list.Add(new SharedBan( ban.Id, - ban.UserId, - ip, - hwid, + ban.Type, + ban.UserIds, + ips, + hwids, ban.BanTime.UtcDateTime, ban.ExpirationTime?.UtcDateTime, ban.Reason, @@ -129,7 +96,7 @@ private async Task LoadRoleBans(NetUserId userId) ? null : (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username, unban, - ban.Role + ban.Roles )); } } @@ -144,7 +111,6 @@ private async Task LoadFromDb() string.Empty; await LoadBans(userId); - await LoadRoleBans(userId); StateDirty(); } diff --git a/Content.Server/Administration/BanPanelEui.cs b/Content.Server/Administration/BanPanelEui.cs index 0a09ad557f5..f1364483a69 100644 --- a/Content.Server/Administration/BanPanelEui.cs +++ b/Content.Server/Administration/BanPanelEui.cs @@ -29,8 +29,8 @@ public sealed class BanPanelEui : BaseEui private string PlayerName { get; set; } = string.Empty; private IPAddress? LastAddress { get; set; } private ImmutableTypedHwid? LastHwid { get; set; } - private const int Ipv4_CIDR = 32; - private const int Ipv6_CIDR = 64; + private const int Ipv4_CIDR = CreateBanInfo.DefaultMaskIpv4; + private const int Ipv6_CIDR = CreateBanInfo.DefaultMaskIpv6; public BanPanelEui() { @@ -73,6 +73,15 @@ private async void BanPlayer(string? target, string? ipAddressString, bool useLa return; } + var isRoleBan = roles?.Count > 0; + + CreateBanInfo banInfo = isRoleBan ? new CreateRoleBanInfo(reason) : new CreateServerBanInfo(reason); + + banInfo.WithBanningAdmin(Player.UserId); + banInfo.WithSeverity(severity); + if (minutes > 0) + banInfo.WithMinutes(minutes); + (IPAddress, int)? addressRange = null; if (ipAddressString is not null) { @@ -119,14 +128,26 @@ private async void BanPlayer(string? target, string? ipAddressString, bool useLa targetHWid = useLastHwid ? located.LastHWId : hwid; } - if (roles?.Count > 0) + if (addressRange != null) + banInfo.AddAddressRange(addressRange.Value); + + if (targetUid != null) + banInfo.AddUser(targetUid.Value, target!); + + banInfo.AddHWId(targetHWid); + + if (isRoleBan) { - var now = DateTimeOffset.UtcNow; - foreach (var role in roles) + var roleBanInfo = (CreateRoleBanInfo)banInfo; + foreach (var role in roles ?? []) { if (_prototypeManager.HasIndex(role)) { - _banManager.CreateRoleBan(targetUid, target, Player.UserId, addressRange, targetHWid, role, minutes, severity, reason, now); + roleBanInfo.AddJob(new ProtoId(role)); + } + else if (_prototypeManager.HasIndex(role)) + { + roleBanInfo.AddAntag(new ProtoId(role)); } else { @@ -134,25 +155,25 @@ private async void BanPlayer(string? target, string? ipAddressString, bool useLa } } - Close(); - return; + _banManager.CreateRoleBan(roleBanInfo); } - - if (erase && - targetUid != null) + else { - try - { - if (_entities.TrySystem(out AdminSystem? adminSystem)) - adminSystem.Erase(targetUid.Value); - } - catch (Exception e) + if (erase && targetUid is not null) { - _sawmill.Error($"Error while erasing banned player:\n{e}"); + try + { + if (_entities.TrySystem(out AdminSystem? adminSystem)) + adminSystem.Erase(targetUid.Value); + } + catch (Exception e) + { + _sawmill.Error($"Error while erasing banned player:\n{e}"); + } } - } - _banManager.CreateServerBan(targetUid, target, Player.UserId, addressRange, targetHWid, minutes, severity, reason); + _banManager.CreateServerBan((CreateServerBanInfo)banInfo); + } Close(); } diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index f76cfded814..a6d3b106462 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -90,7 +90,15 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] var targetUid = located.UserId; var targetHWid = located.LastHWId; - _bans.CreateServerBan(targetUid, target, player?.UserId, null, targetHWid, minutes, severity, reason); + var banInfo = new CreateServerBanInfo(reason); + banInfo.WithBanningAdmin(player?.UserId); + banInfo.AddUser(targetUid, target); + banInfo.AddHWId(targetHWid); + if (minutes > 0) + banInfo.WithMinutes(minutes); + banInfo.WithSeverity(severity); + + _bans.CreateServerBan(banInfo); } public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) diff --git a/Content.Server/Administration/Commands/BanListCommand.cs b/Content.Server/Administration/Commands/BanListCommand.cs index ea68788deb5..26a8647248a 100644 --- a/Content.Server/Administration/Commands/BanListCommand.cs +++ b/Content.Server/Administration/Commands/BanListCommand.cs @@ -39,7 +39,7 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] if (shell.Player is not { } player) { - var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false); + var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false); if (bans.Count == 0) { diff --git a/Content.Server/Administration/Commands/DepartmentBanCommand.cs b/Content.Server/Administration/Commands/DepartmentBanCommand.cs index 49da023cd9d..851053d0dec 100644 --- a/Content.Server/Administration/Commands/DepartmentBanCommand.cs +++ b/Content.Server/Administration/Commands/DepartmentBanCommand.cs @@ -130,13 +130,19 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) var targetUid = located.UserId; var targetHWid = located.LastHWId; - // If you are trying to remove the following variable, please don't. It's there because the note system groups role bans by time, reason and banning admin. - // Without it the note list will get needlessly cluttered. - var now = DateTimeOffset.UtcNow; + var banInfo = new CreateRoleBanInfo(reason); + if (minutes > 0) + banInfo.WithMinutes(minutes); + banInfo.AddUser(targetUid, located.Username); + banInfo.WithBanningAdmin(shell.Player?.UserId); + banInfo.AddHWId(targetHWid); + banInfo.WithSeverity(severity); + foreach (var job in departmentProto.Roles) { - _banManager.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, now); + banInfo.AddJob(job); } + // Discord Ban Webhook DeadSpace DateTimeOffset? expires = null; if (minutes > 0) @@ -168,40 +174,75 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) await _httpClient.PostAsync($"{webhookUrl}?wait=true", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); } + + _banManager.CreateRoleBan(banInfo); + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + var durOpts = new CompletionOption[] + { + new("0", Loc.GetString("cmd-roleban-hint-duration-1")), + new("1440", Loc.GetString("cmd-roleban-hint-duration-2")), + new("4320", Loc.GetString("cmd-roleban-hint-duration-3")), + new("10080", Loc.GetString("cmd-roleban-hint-duration-4")), + new("20160", Loc.GetString("cmd-roleban-hint-duration-5")), + new("43800", Loc.GetString("cmd-roleban-hint-duration-6")), + }; + + var severities = new CompletionOption[] + { + new("none", Loc.GetString("admin-note-editor-severity-none")), + new("minor", Loc.GetString("admin-note-editor-severity-low")), + new("medium", Loc.GetString("admin-note-editor-severity-medium")), + new("high", Loc.GetString("admin-note-editor-severity-high")), + }; + + return args.Length switch + { + 1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), + Loc.GetString("cmd-roleban-hint-1")), + 2 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs(), + Loc.GetString("cmd-roleban-hint-2")), + 3 => CompletionResult.FromHint(Loc.GetString("cmd-roleban-hint-3")), + 4 => CompletionResult.FromHintOptions(durOpts, Loc.GetString("cmd-roleban-hint-4")), + 5 => CompletionResult.FromHintOptions(severities, Loc.GetString("cmd-roleban-hint-5")), + _ => CompletionResult.Empty + }; } private string GenerateBanDescription(string target, ICommonSession? player, uint minutes, string reason, DateTimeOffset? expires, string department) { var builder = new StringBuilder(); - builder.AppendLine($"### **����������-��� **"); - builder.AppendLine($"**����������:** *{target}*"); - builder.AppendLine($"**�������:** {reason}"); + builder.AppendLine($"### **Департамент-бан**"); + builder.AppendLine($"**Нарушитель:** *{target}*"); + builder.AppendLine($"**Причина:** {reason}"); var banDuration = TimeSpan.FromMinutes(minutes); - builder.Append($"**������������:** "); + builder.Append($"**Длительность:** "); if (expires != null) { - builder.Append($"{banDuration.Days} {NumWord(banDuration.Days, "����", "���", "����")}, "); - builder.Append($"{banDuration.Hours} {NumWord(banDuration.Hours, "���", "����", "�����")}, "); - builder.AppendLine($"{banDuration.Minutes} {NumWord(banDuration.Minutes, "������", "������", "�����")}"); + builder.Append($"{banDuration.Days} {NumWord(banDuration.Days, "день", "дня", "дней")}, "); + builder.Append($"{banDuration.Hours} {NumWord(banDuration.Hours, "час", "часа", "часов")}, "); + builder.AppendLine($"{banDuration.Minutes} {NumWord(banDuration.Minutes, "минута", "минуты", "минут")}"); } else { - builder.AppendLine($"***��������***"); + builder.AppendLine($"***Навсегда***"); } - builder.AppendLine($"**�����:** {department}"); + builder.AppendLine($"**Отдел:** {department}"); if (expires != null) { - builder.AppendLine($"**���� ������ ���������:** {expires}"); + builder.AppendLine($"**Дата снятия наказания:** {expires}"); } - builder.Append($"**��������� �����(-�):** "); + builder.Append($"**Наказание выдал(-а):** "); if (player != null) { @@ -209,7 +250,7 @@ private string GenerateBanDescription(string target, ICommonSession? player, uin } else { - builder.AppendLine($"***�������***"); + builder.AppendLine($"***СИСТЕМА***"); } return builder.ToString(); @@ -238,37 +279,4 @@ private string NumWord(int value, params string[] words) return words[2]; } - public CompletionResult GetCompletion(IConsoleShell shell, string[] args) - { - var durOpts = new CompletionOption[] - { - new("0", Loc.GetString("cmd-roleban-hint-duration-1")), - new("1440", Loc.GetString("cmd-roleban-hint-duration-2")), - new("4320", Loc.GetString("cmd-roleban-hint-duration-3")), - new("10080", Loc.GetString("cmd-roleban-hint-duration-4")), - new("20160", Loc.GetString("cmd-roleban-hint-duration-5")), - new("43800", Loc.GetString("cmd-roleban-hint-duration-6")), - }; - - var severities = new CompletionOption[] - { - new("none", Loc.GetString("admin-note-editor-severity-none")), - new("minor", Loc.GetString("admin-note-editor-severity-low")), - new("medium", Loc.GetString("admin-note-editor-severity-medium")), - new("high", Loc.GetString("admin-note-editor-severity-high")), - }; - - return args.Length switch - { - 1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), - Loc.GetString("cmd-roleban-hint-1")), - 2 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs(), - Loc.GetString("cmd-roleban-hint-2")), - 3 => CompletionResult.FromHint(Loc.GetString("cmd-roleban-hint-3")), - 4 => CompletionResult.FromHintOptions(durOpts, Loc.GetString("cmd-roleban-hint-4")), - 5 => CompletionResult.FromHintOptions(severities, Loc.GetString("cmd-roleban-hint-5")), - _ => CompletionResult.Empty - }; - } - } diff --git a/Content.Server/Administration/Commands/OpenAdminNotesCommand.cs b/Content.Server/Administration/Commands/OpenAdminNotesCommand.cs index 5577e134371..35a6d1096aa 100644 --- a/Content.Server/Administration/Commands/OpenAdminNotesCommand.cs +++ b/Content.Server/Administration/Commands/OpenAdminNotesCommand.cs @@ -3,6 +3,7 @@ using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; +using Robust.Shared.Network; namespace Content.Server.Administration.Commands; @@ -46,7 +47,7 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] return; } - await _adminNotes.OpenEui(player, notedPlayer); + await _adminNotes.OpenEui(player, new NetUserId(notedPlayer)); } public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) diff --git a/Content.Server/Administration/Commands/PardonCommand.cs b/Content.Server/Administration/Commands/PardonCommand.cs index 5c4417a966f..eb292663e2e 100644 --- a/Content.Server/Administration/Commands/PardonCommand.cs +++ b/Content.Server/Administration/Commands/PardonCommand.cs @@ -27,7 +27,7 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] return; } - var ban = await _dbManager.GetServerBanAsync(banId); + var ban = await _dbManager.GetBanAsync(banId); if (ban == null) { @@ -50,7 +50,7 @@ public override async void Execute(IConsoleShell shell, string argStr, string[] return; } - await _dbManager.AddServerUnbanAsync(new ServerUnbanDef(banId, player?.UserId, DateTimeOffset.Now)); + await _dbManager.AddUnbanAsync(new UnbanDef(banId, player?.UserId, DateTimeOffset.Now)); shell.WriteLine(Loc.GetString($"cmd-pardon-success", ("id", banId))); } diff --git a/Content.Server/Administration/Commands/RoleBanCommand.cs b/Content.Server/Administration/Commands/RoleBanCommand.cs index 793d4b401ba..b77ce54e27f 100644 --- a/Content.Server/Administration/Commands/RoleBanCommand.cs +++ b/Content.Server/Administration/Commands/RoleBanCommand.cs @@ -56,7 +56,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) }; string target; - string job; + string role; string reason; uint minutes; if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), out NoteSeverity severity)) @@ -70,13 +70,13 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) { case 3: target = args[0]; - job = args[1]; + role = args[1]; reason = args[2]; minutes = 0; break; case 4: target = args[0]; - job = args[1]; + role = args[1]; reason = args[2]; if (!uint.TryParse(args[3], out minutes)) @@ -88,7 +88,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) break; case 5: target = args[0]; - job = args[1]; + role = args[1]; reason = args[2]; if (!uint.TryParse(args[3], out minutes)) @@ -110,12 +110,6 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (!_proto.HasIndex(job)) - { - shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", job))); - return; - } - var located = await _locator.LookupIdByNameOrIdAsync(target); if (located == null) { @@ -126,7 +120,29 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) var targetUid = located.UserId; var targetHWid = located.LastHWId; - _bans.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, DateTimeOffset.UtcNow); + var banInfo = new CreateRoleBanInfo(reason); + if (minutes > 0) + banInfo.WithMinutes(minutes); + banInfo.AddUser(targetUid, located.Username); + banInfo.WithBanningAdmin(shell.Player?.UserId); + banInfo.AddHWId(targetHWid); + banInfo.WithSeverity(severity); + + if (_proto.HasIndex(role)) + { + banInfo.AddJob(new ProtoId(role)); + } + else if (_proto.HasIndex(role)) + { + banInfo.AddAntag(new ProtoId(role)); + } + else + { + shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", role))); + return; + } + + _bans.CreateRoleBan(banInfo); } public CompletionResult GetCompletion(IConsoleShell shell, string[] args) diff --git a/Content.Server/Administration/Commands/RoleBanListCommand.cs b/Content.Server/Administration/Commands/RoleBanListCommand.cs index 8244ded3b20..4abd406cbc0 100644 --- a/Content.Server/Administration/Commands/RoleBanListCommand.cs +++ b/Content.Server/Administration/Commands/RoleBanListCommand.cs @@ -1,10 +1,8 @@ -using System.Linq; -using System.Text; -using Content.Server.Administration.BanList; +using Content.Server.Administration.BanList; using Content.Server.EUI; using Content.Server.Database; using Content.Shared.Administration; -using Robust.Server.Player; +using Content.Shared.Database; using Robust.Shared.Console; namespace Content.Server.Administration.Commands; @@ -48,7 +46,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) if (shell.Player is not { } player) { - var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned); + var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned, type: BanType.Role); if (bans.Count == 0) { @@ -58,7 +56,7 @@ public async void Execute(IConsoleShell shell, string argStr, string[] args) foreach (var ban in bans) { - var msg = $"ID: {ban.Id}: Role: {ban.Role} Reason: {ban.Reason}"; + var msg = $"ID: {ban.Id}: Role(s): {string.Join(",", ban.Roles ?? [])} Reason: {ban.Reason}"; shell.WriteLine(msg); } return; diff --git a/Content.Server/Administration/Managers/BanManager.Notification.cs b/Content.Server/Administration/Managers/BanManager.Notification.cs index ff84887f00d..d627dc508f1 100644 --- a/Content.Server/Administration/Managers/BanManager.Notification.cs +++ b/Content.Server/Administration/Managers/BanManager.Notification.cs @@ -41,14 +41,8 @@ private bool OnDatabaseNotificationEarlyFilter() private async void ProcessBanNotification(BanNotificationData data) { - if ((await _entryManager.ServerEntity).Id == data.ServerId) - { - _sawmill.Verbose("Not processing ban notification: came from this server"); - return; - } - _sawmill.Verbose($"Processing ban notification for ban {data.BanId}"); - var ban = await _db.GetServerBanAsync(data.BanId); + var ban = await _db.GetBanAsync(data.BanId); if (ban == null) { _sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?"); @@ -86,15 +80,5 @@ private sealed class BanNotificationData /// [JsonRequired, JsonPropertyName("ban_id")] public int BanId { get; init; } - - /// - /// The id of the server the ban was made on. - /// This is used to avoid double work checking the ban on the originating server. - /// - /// - /// This is optional in case the ban was made outside a server (SS14.Admin) - /// - [JsonPropertyName("server_id")] - public int? ServerId { get; init; } } } diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index fe92bbdfe7e..e8147e2fa81 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using System.Linq; -using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,6 +22,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Administration.Managers; @@ -34,7 +34,6 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit [Dependency] private readonly IEntitySystemManager _systems = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly ILocalizationManager _localizationManager = default!; - [Dependency] private readonly ServerDbEntryManager _entryManager = default!; [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly ILogManager _logManager = default!; @@ -46,9 +45,10 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit private ISawmill _sawmill = default!; public const string SawmillId = "admin.bans"; - public const string JobPrefix = "Job:"; + public const string DbTypeAntag = "Antag"; + public const string DbTypeJob = "Job"; - private readonly Dictionary> _cachedRoleBans = new(); + private readonly Dictionary> _cachedRoleBans = new(); // Cached ban exemption flags are used to handle private readonly Dictionary _cachedBanExemptions = new(); @@ -74,9 +74,15 @@ private async Task CachePlayerData(ICommonSession player, CancellationToken canc var netChannel = player.Channel; ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId; var modernHwids = netChannel.UserData.ModernHWIds; - var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false); + var roleBans = await _db.GetBansAsync( + netChannel.RemoteEndPoint.Address, + player.UserId, + hwId, + modernHwids, + false, + type: BanType.Role); - var userRoleBans = new List(); + var userRoleBans = new List(); foreach (var ban in roleBans) { userRoleBans.Add(ban); @@ -94,28 +100,18 @@ private void ClearPlayerData(ICommonSession player) _cachedBanExemptions.Remove(player); } - private async Task AddRoleBan(ServerRoleBanDef banDef) + private async Task AddRoleBan(BanDef banDef) { - banDef = await _db.AddServerRoleBanAsync(banDef); + banDef = await _db.AddBanAsync(banDef); - if (banDef.UserId != null - && _playerManager.TryGetSessionById(banDef.UserId, out var player) - && _cachedRoleBans.TryGetValue(player, out var cachedBans)) + foreach (var user in banDef.UserIds) { - cachedBans.Add(banDef); + if (_playerManager.TryGetSessionById(user, out var player) + && _cachedRoleBans.TryGetValue(player, out var cachedBans)) + { + cachedBans.Add(banDef); + } } - - return true; - } - - public HashSet? GetRoleBans(NetUserId playerUserId) - { - if (!_playerManager.TryGetSessionById(playerUserId, out var session)) - return null; - - return _cachedRoleBans.TryGetValue(session, out var roleBans) - ? roleBans.Select(banDef => banDef.Role).ToHashSet() - : null; } public void Restart() @@ -141,43 +137,37 @@ public void Restart() } #region Server Bans - public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason) + public async void CreateServerBan(CreateServerBanInfo banInfo) { - DateTimeOffset? expires = null; - if (minutes > 0) + var (banDef, expires) = await CreateBanDef(banInfo, BanType.Server, null); + + await _db.AddBanAsync(banDef); + + if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules)) { - expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value); + // Reset their last read rules. They probably need a refresher! + foreach (var (userId, _) in banInfo.Users) + { + await _db.SetLastReadRules(userId, null); + } } - _systems.TryGetEntitySystem(out var ticker); - int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; - var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; - - var banDef = new ServerBanDef( - null, - target, - addressRange, - hwid, - DateTimeOffset.Now, - expires, - roundId, - playtime, - reason, - severity, - banningAdmin, - null); - - await _db.AddServerBanAsync(banDef); - if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null) - await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher! - var adminName = banningAdmin == null + var adminName = banInfo.BanningAdmin == null ? Loc.GetString("system-user") - : (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user"); - var targetName = target is null ? "null" : $"{targetUsername} ({target})"; - var addressRangeString = addressRange != null - ? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}" - : "null"; - var hwidString = hwid?.ToString() ?? "null"; + : (await _db.GetPlayerRecordByUserId(banInfo.BanningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user"); + + var targetName = banInfo.Users.Count == 0 + ? "null" + : string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})")); + + var addressRangeString = banInfo.AddressRanges.Count != 0 + ? "null" + : string.Join(", ", banInfo.AddressRanges.Select(a => $"{a.Address}/{a.Mask}")); + + var hwidString = banInfo.HWIds.Count == 0 + ? "null" + : string.Join(", ", banInfo.HWIds); + var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}"; var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii"; @@ -185,12 +175,12 @@ public async void CreateServerBan(NetUserId? target, string? targetUsername, Net var logMessage = Loc.GetString( key, ("admin", adminName), - ("severity", severity), + ("severity", banDef.Severity), ("expires", expiresString), ("name", targetName), ("ip", addressRangeString), ("hwid", hwidString), - ("reason", reason)); + ("reason", banInfo.Reason)); _sawmill.Info(logMessage); _chat.SendAdminAlert(logMessage); @@ -199,7 +189,19 @@ public async void CreateServerBan(NetUserId? target, string? targetUsername, Net KickMatchingConnectedPlayers(banDef, "newly placed ban"); } - private void KickMatchingConnectedPlayers(ServerBanDef def, string source) + private NoteSeverity GetSeverityForServerBan(CreateBanInfo banInfo, CVarDef defaultCVar) + { + if (banInfo.Severity != null) + return banInfo.Severity.Value; + + if (Enum.TryParse(_cfg.GetCVar(defaultCVar), true, out NoteSeverity parsedSeverity)) + return parsedSeverity; + + _sawmill.Error($"CVar {defaultCVar.Name} has invalid ban severity!"); + return NoteSeverity.None; + } + + private void KickMatchingConnectedPlayers(BanDef def, string source) { foreach (var player in _playerManager.Sessions) { @@ -211,7 +213,7 @@ private void KickMatchingConnectedPlayers(ServerBanDef def, string source) } } - private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban) + private bool BanMatchesPlayer(ICommonSession player, BanDef ban) { var playerInfo = new BanMatcher.PlayerInfo { @@ -228,7 +230,7 @@ private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban) return BanMatcher.BanMatches(ban, playerInfo); } - private void KickForBanDef(ICommonSession player, ServerBanDef def) + private void KickForBanDef(ICommonSession player, BanDef def) { var message = def.FormatBanMessage(_cfg, _localizationManager); player.Channel.Disconnect(message); @@ -245,66 +247,144 @@ private void PlayBanSound() #endregion - #region Job Bans - // If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin. - // Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset. - public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan) + #region Role Bans + + public async void CreateRoleBan(CreateRoleBanInfo banInfo) { - if (!_prototypeManager.TryIndex(role, out JobPrototype? _)) + ImmutableArray roleDefs = + [ + .. ToBanRoleDef(banInfo.JobPrototypes), + .. ToBanRoleDef(banInfo.AntagPrototypes), + ]; + + if (roleDefs.Length == 0) + throw new ArgumentException("Must specify at least one role to ban!"); + + var (banDef, expires) = await CreateBanDef(banInfo, BanType.Role, roleDefs); + + await AddRoleBan(banDef); + + var length = expires == null + ? Loc.GetString("cmd-roleban-inf") + : Loc.GetString("cmd-roleban-until", ("expires", expires)); + + var targetName = banInfo.Users.Count == 0 + ? "null" + : string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})")); + + var rolesList = string.Join(", ", roleDefs.Select(r => r.RoleId)); + + _chat.SendAdminAlert(Loc.GetString( + "cmd-roleban-success", + ("target", targetName), + ("role", rolesList), + ("reason", banInfo.Reason), + ("length", length))); + + foreach (var (userId, _) in banInfo.Users) { - throw new ArgumentException($"Invalid role '{role}'", nameof(role)); + if (_playerManager.TryGetSessionById(userId, out var session)) + SendRoleBans(session); } + } + + private async Task<(BanDef Ban, DateTimeOffset? Expires)> CreateBanDef( + CreateBanInfo banInfo, + BanType type, + ImmutableArray? roleBans) + { + if (banInfo.Users.Count == 0 && banInfo.HWIds.Count == 0 && banInfo.AddressRanges.Count == 0) + throw new ArgumentException("Must specify at least one user, HWID, or address range"); - role = string.Concat(JobPrefix, role); DateTimeOffset? expires = null; - if (minutes > 0) + if (banInfo.Duration is { } duration) + expires = DateTimeOffset.Now + duration; + + ImmutableArray roundIds; + if (banInfo.RoundIds.Count > 0) { - expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value); + roundIds = [..banInfo.RoundIds]; + } + else if (_systems.TryGetEntitySystem(out var ticker) && ticker.RoundId != 0) + { + roundIds = [ticker.RoundId]; + } + else + { + roundIds = []; } - _systems.TryGetEntitySystem(out GameTicker? ticker); - int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; - var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; - - var banDef = new ServerRoleBanDef( + return (new BanDef( null, - target, - addressRange, - hwid, - timeOfBan, + type, + [..banInfo.Users.Select(u => u.UserId)], + [..banInfo.AddressRanges], + [..banInfo.HWIds], + DateTimeOffset.Now, expires, - roundId, - playtime, - reason, - severity, - banningAdmin, + roundIds, + await GetPlayTime(banInfo), + banInfo.Reason, + GetSeverityForServerBan(banInfo, CCVars.ServerBanDefaultSeverity), + banInfo.BanningAdmin, null, - role); + roles: roleBans), expires); + } - if (!await AddRoleBan(banDef)) - { - _chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role))); - return; - } + private async Task GetPlayTime(CreateBanInfo banInfo) + { + var firstPlayer = banInfo.Users.FirstOrNull()?.UserId; + if (firstPlayer == null) + return TimeSpan.Zero; - var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires)); - _chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length))); + return (await _db.GetPlayTimes(firstPlayer.Value)) + .Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall) + ?.TimeSpent ?? TimeSpan.Zero; + } - if (target != null && _playerManager.TryGetSessionById(target.Value, out var session)) + private IEnumerable ToBanRoleDef(IEnumerable> protoIds) where T : class, IPrototype + { + return protoIds.Select(protoId => { - SendRoleBans(session); - } + // TODO: I have no idea if this check is necessary. The previous code was a complete mess, + // so out of safety I'm leaving this in. + if (_prototypeManager.HasIndex(protoId) && _prototypeManager.HasIndex(protoId)) + { + throw new InvalidOperationException( + $"Creating role ban for {protoId}: cannot create role ban, role is both JobPrototype and AntagPrototype."); + } + + // Don't trust the input: make sure the role actually exists. + if (!_prototypeManager.HasIndex(protoId)) + throw new UnknownPrototypeException(protoId, typeof(T)); + + return new BanRoleDef(PrototypeKindToDbType(), protoId); + }); + } + + private static string PrototypeKindToDbType() where T : class, IPrototype + { + if (typeof(T) == typeof(JobPrototype)) + return DbTypeJob; + + if (typeof(T) == typeof(AntagPrototype)) + return DbTypeAntag; + + throw new ArgumentException($"Unknown prototype kind for role bans: {typeof(T)}"); } public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime) { - var ban = await _db.GetServerRoleBanAsync(banId); + var ban = await _db.GetBanAsync(banId); if (ban == null) { return $"No ban found with id {banId}"; } + if (ban.Type != BanType.Role) + throw new InvalidOperationException("Ban was not a role ban!"); + if (ban.Unban != null) { var response = new StringBuilder("This ban has already been pardoned"); @@ -318,46 +398,107 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da return response.ToString(); } - await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now)); + await _db.AddUnbanAsync(new UnbanDef(banId, unbanningAdmin, DateTimeOffset.Now)); - if (ban.UserId is { } player - && _playerManager.TryGetSessionById(player, out var session) - && _cachedRoleBans.TryGetValue(session, out var roleBans)) + foreach (var user in ban.UserIds) { - roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id); - SendRoleBans(session); + if (_playerManager.TryGetSessionById(user, out var session) + && _cachedRoleBans.TryGetValue(session, out var roleBans)) + { + roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id); + SendRoleBans(session); + } + } return $"Pardoned ban with id {banId}"; } public HashSet>? GetJobBans(NetUserId playerUserId) + { + return GetRoleBans(playerUserId); + } + + public HashSet>? GetAntagBans(NetUserId playerUserId) + { + return GetRoleBans(playerUserId); + } + + private HashSet>? GetRoleBans(NetUserId playerUserId) where T : class, IPrototype { if (!_playerManager.TryGetSessionById(playerUserId, out var session)) return null; - if (!_cachedRoleBans.TryGetValue(session, out var roleBans)) + return GetRoleBans(session); + } + + private HashSet>? GetRoleBans(ICommonSession playerSession) where T : class, IPrototype + { + if (!_cachedRoleBans.TryGetValue(playerSession, out var roleBans)) return null; + var dbType = PrototypeKindToDbType(); + return roleBans - .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) + .SelectMany(ban => ban.Roles!.Value) + .Where(role => role.RoleType == dbType) + .Select(role => new ProtoId(role.RoleId)) .ToHashSet(); } - #endregion + + public HashSet? GetRoleBans(NetUserId playerUserId) + { + if (!_playerManager.TryGetSessionById(playerUserId, out var session)) + return null; + + return _cachedRoleBans.TryGetValue(session, out var roleBans) + ? roleBans.SelectMany(banDef => banDef.Roles ?? []).ToHashSet() + : null; + } + + public bool IsRoleBanned(ICommonSession player, List> jobs) + { + return IsRoleBanned(player, jobs); + } + + public bool IsRoleBanned(ICommonSession player, List> antags) + { + return IsRoleBanned(player, antags); + } + + private bool IsRoleBanned(ICommonSession player, List> roles) where T : class, IPrototype + { + var bans = GetRoleBans(player.UserId); + + if (bans is null || bans.Count == 0) + return false; + + var dbType = PrototypeKindToDbType(); + + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var role in roles) + { + if (bans.Contains(new BanRoleDef(dbType, role))) + return true; + } + + return false; + } public void SendRoleBans(ICommonSession pSession) { - var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List(); var bans = new MsgRoleBans() { - Bans = roleBans.Select(o => o.Role).ToList() + JobBans = (GetRoleBans(pSession) ?? []).ToList(), + AntagBans = (GetRoleBans(pSession) ?? []).ToList(), }; _sawmill.Debug($"Sent rolebans to {pSession.Name}"); _netManager.ServerSendMessage(bans, pSession.Channel); } + #endregion + public void PostInject() { _sawmill = _logManager.GetSawmill(SawmillId); diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index fc192cc3066..c32451ae603 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Net; +using System.Net.Sockets; using System.Threading.Tasks; using Content.Shared.Database; using Content.Shared.Roles; @@ -14,6 +15,11 @@ public interface IBanManager public void Initialize(); public void Restart(); + /// + /// Create a server ban in the database, blocking connection for matching players. + /// + void CreateServerBan(CreateServerBanInfo banInfo); + /// /// Bans the specified target, address range and / or HWID. One of them must be non-null /// @@ -24,23 +30,78 @@ public interface IBanManager /// Number of minutes to ban for. 0 and null mean permanent /// Severity of the resulting ban note /// Reason for the ban - public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason); - public HashSet? GetRoleBans(NetUserId playerUserId); + [Obsolete("Use CreateServerBan(CreateBanInfo) instead")] + public void CreateServerBan(NetUserId? target, + string? targetUsername, + NetUserId? banningAdmin, + (IPAddress, int)? addressRange, + ImmutableTypedHwid? hwid, + uint? minutes, + NoteSeverity severity, + string reason) + { + var info = new CreateServerBanInfo(reason); + if (target != null) + { + ArgumentNullException.ThrowIfNull(targetUsername); + info.AddUser(target.Value, targetUsername); + } + + if (addressRange != null) + info.AddAddressRange(addressRange.Value); + + if (hwid != null) + info.AddHWId(hwid); + + if (minutes > 0) + info.WithMinutes(minutes.Value); + + if (banningAdmin != null) + info.WithBanningAdmin(banningAdmin.Value); + + info.WithSeverity(severity); + + CreateServerBan(info); + } + + /// + /// Gets a list of prefixed prototype IDs with the player's role bans. + /// + public HashSet? GetRoleBans(NetUserId playerUserId); + + /// + /// Checks if the player is currently banned from any of the listed roles. + /// + /// The player. + /// A list of valid antag prototype IDs. + /// Returns True if an active role ban is found for this player for any of the listed roles. + public bool IsRoleBanned(ICommonSession player, List> antags); + + /// + /// Checks if the player is currently banned from any of the listed roles. + /// + /// The player. + /// A list of valid job prototype IDs. + /// Returns True if an active role ban is found for this player for any of the listed roles. + public bool IsRoleBanned(ICommonSession player, List> jobs); + + /// + /// Gets a list of prototype IDs with the player's job bans. + /// public HashSet>? GetJobBans(NetUserId playerUserId); /// - /// Creates a job ban for the specified target, username or GUID + /// Gets a list of prototype IDs with the player's antag bans. /// - /// Target user, username or GUID, null for none - /// Role to be banned from - /// Severity of the resulting ban note - /// Reason for the ban - /// Number of minutes to ban for. 0 and null mean permanent - /// Time when the ban was applied, used for grouping role bans - public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan); + public HashSet>? GetAntagBans(NetUserId playerUserId); + + /// + /// Creates a role ban, preventing matching players from playing said roles. + /// + public void CreateRoleBan(CreateRoleBanInfo banInfo); /// - /// Pardons a role ban for the specified target, username or GUID + /// Pardons a role ban by its ID. /// /// The id of the role ban to pardon. /// The admin, if any, that pardoned the role ban. @@ -53,3 +114,287 @@ public interface IBanManager /// Player's session public void SendRoleBans(ICommonSession pSession); } + +/// +/// Base info to fill out in created ban records. +/// +/// +/// +[Access(typeof(BanManager), Other = AccessPermissions.Execute)] +public abstract class CreateBanInfo +{ + [Access(Other = AccessPermissions.Read)] + public const int DefaultMaskIpv4 = 32; + [Access(Other = AccessPermissions.Read)] + public const int DefaultMaskIpv6 = 64; + + internal readonly HashSet<(NetUserId UserId, string UserName)> Users = []; + internal readonly HashSet<(IPAddress Address, int Mask)> AddressRanges = []; + internal readonly HashSet HWIds = []; + internal readonly HashSet RoundIds = []; + internal TimeSpan? Duration; + internal NoteSeverity? Severity; + internal string Reason; + internal NetUserId? BanningAdmin; + + protected CreateBanInfo(string reason) + { + Reason = reason; + } + + /// + /// Add a user to be matched by the ban. + /// + /// + /// Bans can target multiple users at once. + /// + /// The ID of the user. + /// The name of the user (used for logging purposes). + /// The current object, for easy chaining. + public CreateBanInfo AddUser(NetUserId userId, string username) + { + Users.Add((userId, username)); + return this; + } + + /// + /// Add an IP address to be matched by the ban. + /// + /// + /// Bans can target multiple addresses at once. + /// + /// + /// The IP address to add. If null, nothing is done. + /// + /// The current object, for easy chaining. + public CreateBanInfo AddAddress(IPAddress? address) + { + if (address == null) + return this; + + return AddAddressRange( + address, + address.AddressFamily == AddressFamily.InterNetwork ? DefaultMaskIpv4 : DefaultMaskIpv6); + } + + /// + /// Add an IP address range to be matched by the ban. + /// + /// + /// Bans can target multiple address ranges at once. + /// + /// The current object, for easy chaining. + public CreateBanInfo AddAddressRange((IPAddress Address, int Mask) addressRange) + { + return AddAddressRange(addressRange.Address, addressRange.Mask); + } + + /// + /// Add an IP address range to be matched by the ban. + /// + /// + /// Bans can target multiple address ranges at once. + /// + /// The current object, for easy chaining. + public CreateBanInfo AddAddressRange(IPAddress address, int mask) + { + AddressRanges.Add((address, mask)); + return this; + } + + /// + /// Add a hardware IP (HWID) to be matched by the ban. + /// + /// + /// Bans can target multiple HWIDs at once. + /// + /// + /// The HWID to add. If null, nothing is done. + /// + /// The current object, for easy chaining. + public CreateBanInfo AddHWId(ImmutableTypedHwid? hwId) + { + if (hwId != null) + HWIds.Add(hwId); + + return this; + } + + /// + /// Add a relevant round ID to this ban. + /// + /// + /// + /// If not specified, the current round ID is used for the ban. + /// Therefore, the first call to this function will replace the round ID, + /// and further calls will add additional round IDs. + /// + /// + /// Bans can target multiple round IDs at once. + /// + /// + /// The current object, for easy chaining. + public CreateBanInfo AddRoundId(int roundId) + { + RoundIds.Add(roundId); + return this; + } + + /// + /// Set how long the ban will last, in minutes. + /// + /// + /// If no duration is specified, the ban is permanent. + /// + /// The duration of the ban, in minutes. + /// The current object, for easy chaining. + /// + /// Thrown if is not a positive number. + /// + public CreateBanInfo WithMinutes(int minutes) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes); + return WithMinutes((uint)minutes); + } + + /// + /// Set how long the ban will last, in minutes. + /// + /// + /// If no duration is specified, the ban is permanent. + /// + /// The duration of the ban, in minutes. + /// The current object, for easy chaining. + /// + /// Thrown if is not a positive number. + /// + public CreateBanInfo WithMinutes(uint minutes) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes); + return WithDuration(TimeSpan.FromMinutes(minutes)); + } + + /// + /// Set how long the ban will last. + /// + /// + /// If no duration is specified, the ban is permanent. + /// + /// The duration of the ban. + /// The current object, for easy chaining. + /// + /// Thrown if is not a positive amount of time. + /// + public CreateBanInfo WithDuration(TimeSpan duration) + { + if (duration <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(duration), "Duration must be greater than zero."); + + Duration = duration; + return this; + } + + /// + /// Set the severity of the ban. + /// + /// + /// If no severity is specified, the default is specified through server configuration. + /// + /// + /// The current object, for easy chaining. + public CreateBanInfo WithSeverity(NoteSeverity severity) + { + Severity = severity; + return this; + } + + /// + /// Set the reason for the ban. + /// + /// + /// This replaces the value given via the object constructor. + /// + /// The current object, for easy chaining. + public CreateBanInfo WithReason(string reason) + { + Reason = reason; + return this; + } + + /// + /// Specify the admin responsible for placing the ban. + /// + /// The current object, for easy chaining. + public CreateBanInfo WithBanningAdmin(NetUserId? banningAdmin) + { + BanningAdmin = banningAdmin; + return this; + } +} + +/// +/// Stores info to create server ban records. +/// +/// +[Access(typeof(BanManager), Other = AccessPermissions.Execute)] +public sealed class CreateServerBanInfo : CreateBanInfo +{ + /// The reason for the server ban. + public CreateServerBanInfo(string reason) : base(reason) + { + } +} + +/// +/// Stores info to create role ban records. +/// +/// +[Access(typeof(BanManager), Other = AccessPermissions.Execute)] +public sealed class CreateRoleBanInfo : CreateBanInfo +{ + internal readonly HashSet> AntagPrototypes = []; + internal readonly HashSet> JobPrototypes = []; + + /// The reason for the role ban. + public CreateRoleBanInfo(string reason) : base(reason) + { + } + + /// + /// Add an antag role that will be unavailable for banned players. + /// + /// + /// + /// Bans can have multiple roles at once. + /// + /// + /// While not checked in this function, adding a ban with invalid role IDs will cause a + /// when actually creating the ban. + /// + /// + /// The current object, for easy chaining. + public CreateRoleBanInfo AddAntag(ProtoId protoId) + { + AntagPrototypes.Add(protoId); + return this; + } + + /// + /// Add a job role that will be unavailable for banned players. + /// + /// + /// + /// Bans can have multiple roles at once. + /// + /// + /// While not checked in this function, adding a ban with invalid role IDs will cause a + /// when actually creating the ban. + /// + /// + /// The current object, for easy chaining. + public CreateRoleBanInfo AddJob(ProtoId protoId) + { + JobPrototypes.Add(protoId); + return this; + } +} diff --git a/Content.Server/Administration/Notes/AdminNotesEui.cs b/Content.Server/Administration/Notes/AdminNotesEui.cs index d1297b251d7..5ecb9c774dc 100644 --- a/Content.Server/Administration/Notes/AdminNotesEui.cs +++ b/Content.Server/Administration/Notes/AdminNotesEui.cs @@ -22,7 +22,7 @@ public AdminNotesEui() IoCManager.InjectDependencies(this); } - private Guid NotedPlayer { get; set; } + private NetUserId NotedPlayer { get; set; } private string NotedPlayerName { get; set; } = string.Empty; private bool HasConnectedBefore { get; set; } private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new(); @@ -112,7 +112,7 @@ public override async void HandleMessage(EuiMessageBase msg) } } - public async Task ChangeNotedPlayer(Guid notedPlayer) + public async Task ChangeNotedPlayer(NetUserId notedPlayer) { NotedPlayer = notedPlayer; await LoadFromDb(); @@ -120,7 +120,7 @@ public async Task ChangeNotedPlayer(Guid notedPlayer) private void NoteModified(SharedAdminNote note) { - if (note.Player != NotedPlayer) + if (!note.Players.Contains(NotedPlayer)) return; Notes[(note.Id, note.NoteType)] = note; @@ -129,7 +129,7 @@ private void NoteModified(SharedAdminNote note) private void NoteDeleted(SharedAdminNote note) { - if (note.Player != NotedPlayer) + if (!note.Players.Contains(NotedPlayer)) return; Notes.Remove((note.Id, note.NoteType)); diff --git a/Content.Server/Administration/Notes/AdminNotesExtensions.cs b/Content.Server/Administration/Notes/AdminNotesExtensions.cs index 349c7ff3bdf..e2ec62ed618 100644 --- a/Content.Server/Administration/Notes/AdminNotesExtensions.cs +++ b/Content.Server/Administration/Notes/AdminNotesExtensions.cs @@ -1,3 +1,5 @@ +using System.Collections.Immutable; +using System.Linq; using Content.Server.Database; using Content.Shared.Administration.Notes; using Content.Shared.Database; @@ -11,7 +13,7 @@ public static SharedAdminNote ToShared(this IAdminRemarksRecord note) NoteSeverity? severity = null; var secret = false; NoteType type; - string[]? bannedRoles = null; + ImmutableArray? bannedRoles = null; string? unbannedByName = null; DateTime? unbannedTime = null; bool? seen = null; @@ -30,13 +32,13 @@ public static SharedAdminNote ToShared(this IAdminRemarksRecord note) type = NoteType.Message; seen = adminMessage.Seen; break; - case ServerBanNoteRecord ban: + case BanNoteRecord { Type: BanType.Server } ban: type = NoteType.ServerBan; severity = ban.Severity; unbannedTime = ban.UnbanTime; unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user"); break; - case ServerRoleBanNoteRecord roleBan: + case BanNoteRecord { Type: BanType.Role } roleBan: type = NoteType.RoleBan; severity = roleBan.Severity; bannedRoles = roleBan.Roles; @@ -48,14 +50,14 @@ public static SharedAdminNote ToShared(this IAdminRemarksRecord note) } // There may be bans without a user, but why would we ever be converting them to shared notes? - if (note.Player is null) - throw new ArgumentNullException(nameof(note), "Player user ID cannot be null for a note"); + if (note.Players.Length == 0) + throw new ArgumentNullException(nameof(note), "Player user ID cannot be empty for a note"); return new SharedAdminNote( note.Id, - note.Player!.UserId, - note.Round?.Id, - note.Round?.Server.Name, + [..note.Players.Select(p => p.UserId)], + [..note.Rounds.Select(r => r.Id)], + note.Rounds.SingleOrDefault()?.Server.Name, // TODO: Show all server names? note.PlaytimeAtNote, type, note.Message, diff --git a/Content.Server/Administration/Notes/AdminNotesManager.cs b/Content.Server/Administration/Notes/AdminNotesManager.cs index 7a94ce1a3fa..9e1ed68e059 100644 --- a/Content.Server/Administration/Notes/AdminNotesManager.cs +++ b/Content.Server/Administration/Notes/AdminNotesManager.cs @@ -56,7 +56,7 @@ public bool CanView(ICommonSession admin) return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes); } - public async Task OpenEui(ICommonSession admin, Guid notedPlayer) + public async Task OpenEui(ICommonSession admin, NetUserId notedPlayer) { var ui = new AdminNotesEui(); _euis.OpenEui(ui, admin); @@ -149,8 +149,8 @@ public async Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType var note = new SharedAdminNote( noteId, - (NetUserId) player, - roundId, + [(NetUserId) player], + roundId.HasValue ? [roundId.Value] : [], serverName, playtime, type, @@ -177,8 +177,7 @@ public async Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(), NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(), NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(), - NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(), - NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(), + NoteType.ServerBan or NoteType.RoleBan => (await _db.GetBanAsNoteAsync(id))?.ToShared(), _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type") }; } @@ -205,11 +204,8 @@ public async Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession de case NoteType.Message: await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt); break; - case NoteType.ServerBan: - await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt); - break; - case NoteType.RoleBan: - await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt); + case NoteType.ServerBan or NoteType.RoleBan: + await _db.HideBanFromNotes(noteId, deletedBy.UserId, deletedAt); break; default: throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type"); @@ -285,15 +281,10 @@ public async Task ModifyAdminRemark(int noteId, NoteType type, ICommonSession ed case NoteType.Message: await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime); break; - case NoteType.ServerBan: + case NoteType.ServerBan or NoteType.RoleBan: if (severity is null) throw new ArgumentException("Severity cannot be null for a ban", nameof(severity)); - await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt); - break; - case NoteType.RoleBan: - if (severity is null) - throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity)); - await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt); + await _db.EditBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt); break; default: throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type"); diff --git a/Content.Server/Administration/Notes/IAdminNotesManager.cs b/Content.Server/Administration/Notes/IAdminNotesManager.cs index f54f8a21bda..4e992ba30b1 100644 --- a/Content.Server/Administration/Notes/IAdminNotesManager.cs +++ b/Content.Server/Administration/Notes/IAdminNotesManager.cs @@ -2,6 +2,7 @@ using Content.Server.Database; using Content.Shared.Administration.Notes; using Content.Shared.Database; +using Robust.Shared.Network; using Robust.Shared.Player; namespace Content.Server.Administration.Notes; @@ -16,7 +17,7 @@ public interface IAdminNotesManager bool CanDelete(ICommonSession admin); bool CanEdit(ICommonSession admin); bool CanView(ICommonSession admin); - Task OpenEui(ICommonSession admin, Guid notedPlayer); + Task OpenEui(ICommonSession admin, NetUserId notedPlayer); Task OpenUserNotesEui(ICommonSession player); Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime); Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession deletedBy); diff --git a/Content.Server/Administration/PlayerPanelEui.cs b/Content.Server/Administration/PlayerPanelEui.cs index 31acd33bf12..bfc4990b759 100644 --- a/Content.Server/Administration/PlayerPanelEui.cs +++ b/Content.Server/Administration/PlayerPanelEui.cs @@ -186,11 +186,8 @@ public async void SetPlayerState() { _whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId); // This won't get associated ip or hwid bans but they were not placed on this account anyways - _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count; - // Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally - // The only way to distinguish whether a role ban is the same is to compare the ban time. - // This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now. - _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count(); + _bans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null)).Count; + _roleBans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null, type: BanType.Role)).Count(); } else { diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index c17baea7fd8..dd1e530b2cf 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -180,7 +180,7 @@ private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs } // Check if the user has been banned - var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null); + var ban = await _dbManager.GetBanAsync(null, e.Session.UserId, null, null); if (ban != null) { var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason)); diff --git a/Content.Server/Codewords/CodewordFactionPrototype.cs b/Content.Server/Codewords/CodewordFactionPrototype.cs index 72d24b1dcdf..62552c1966e 100644 --- a/Content.Server/Codewords/CodewordFactionPrototype.cs +++ b/Content.Server/Codewords/CodewordFactionPrototype.cs @@ -10,11 +10,11 @@ public sealed partial class CodewordFactionPrototype : IPrototype { /// [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; /// /// The generator to use for this faction. /// [DataField(required:true)] - public ProtoId Generator { get; } = default!; + public ProtoId Generator { get; private set; } = default!; } diff --git a/Content.Server/Codewords/CodewordGeneratorPrototype.cs b/Content.Server/Codewords/CodewordGeneratorPrototype.cs index 15e50ebf73c..b17a40fc6a2 100644 --- a/Content.Server/Codewords/CodewordGeneratorPrototype.cs +++ b/Content.Server/Codewords/CodewordGeneratorPrototype.cs @@ -11,13 +11,13 @@ public sealed partial class CodewordGeneratorPrototype : IPrototype { /// [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; /// /// List of datasets to use for word generation. All values will be concatenated into one list and then randomly chosen from /// [DataField] - public List> Words { get; } = + public List> Words { get; private set; } = [ "Adjectives", "Verbs", diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs index dabb0f8f1c3..11956124866 100644 --- a/Content.Server/Connection/ConnectionManager.cs +++ b/Content.Server/Connection/ConnectionManager.cs @@ -213,7 +213,7 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame * TODO: Jesus H Christ what is this utter mess of a function * TODO: Break this apart into is constituent steps. */ - private async Task<(ConnectionDenyReason, string, List? bansHit)?> ShouldDeny( + private async Task<(ConnectionDenyReason, string, List? bansHit)?> ShouldDeny( NetConnectingArgs e) { // Check if banned. @@ -234,7 +234,7 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame return (ConnectionDenyReason.NoHwid, Loc.GetString("hwid-required"), null); } - var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false); + var bans = await _db.GetBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false); if (bans.Count > 0) { var firstBan = bans[0]; diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index fac4dcdc8fc..2f9cf457f79 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -1,31 +1,30 @@ - - $(TargetFramework) - 12 - false false ..\bin\Content.Server\ true Exe 1998 RA0032;nullable - enable true + + + - - - - + + + + + diff --git a/Content.Server/Database/BanDef.cs b/Content.Server/Database/BanDef.cs new file mode 100644 index 00000000000..d459b475793 --- /dev/null +++ b/Content.Server/Database/BanDef.cs @@ -0,0 +1,128 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Net; +using Content.Shared.CCVar; +using Content.Shared.Database; +using Robust.Shared.Configuration; +using Robust.Shared.Network; + + +namespace Content.Server.Database +{ + public sealed class BanDef + { + public int? Id { get; } + public BanType Type { get; } + public ImmutableArray UserIds { get; } + public ImmutableArray<(IPAddress address, int cidrMask)> Addresses { get; } + public ImmutableArray HWIds { get; } + + public DateTimeOffset BanTime { get; } + public DateTimeOffset? ExpirationTime { get; } + public ImmutableArray RoundIds { get; } + public TimeSpan PlaytimeAtNote { get; } + public string Reason { get; } + public NoteSeverity Severity { get; set; } + public NetUserId? BanningAdmin { get; } + public UnbanDef? Unban { get; } + public ServerBanExemptFlags ExemptFlags { get; } + + public ImmutableArray? Roles { get; } + + public BanDef( + int? id, + BanType type, + ImmutableArray userIds, + ImmutableArray<(IPAddress address, int cidrMask)> addresses, + ImmutableArray hwIds, + DateTimeOffset banTime, + DateTimeOffset? expirationTime, + ImmutableArray roundIds, + TimeSpan playtimeAtNote, + string reason, + NoteSeverity severity, + NetUserId? banningAdmin, + UnbanDef? unban, + ServerBanExemptFlags exemptFlags = default, + ImmutableArray? roles = null) + { + if (userIds.Length == 0 && addresses.Length == 0 && hwIds.Length == 0) + { + throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID"); + } + + addresses = addresses.Select(address => + { + if (address is { address.IsIPv4MappedToIPv6: true } addr) + { + // Fix IPv6-mapped IPv4 addresses + // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. + address = (addr.address.MapToIPv4(), addr.cidrMask - 96); + } + + return address; + }) + .ToImmutableArray(); + + Id = id; + Type = type; + UserIds = userIds; + Addresses = addresses; + HWIds = hwIds; + BanTime = banTime; + ExpirationTime = expirationTime; + RoundIds = roundIds; + PlaytimeAtNote = playtimeAtNote; + Reason = reason; + Severity = severity; + BanningAdmin = banningAdmin; + Unban = unban; + ExemptFlags = exemptFlags; + + switch (Type) + { + case BanType.Server: + if (roles != null) + throw new ArgumentException("Cannot specify roles for server ban types", nameof(roles)); + break; + + case BanType.Role: + if (roles is not { Length: > 0 }) + throw new ArgumentException("Must specify roles for server ban types", nameof(roles)); + if (exemptFlags != 0) + throw new ArgumentException("Role bans cannot have exempt flags", nameof(exemptFlags)); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + Roles = roles; + } + + public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc) + { + string expires; + if (ExpirationTime is { } expireTime) + { + var duration = expireTime - BanTime; + var utc = expireTime.ToUniversalTime(); + expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f"))); + } + else + { + var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal); + expires = !string.IsNullOrWhiteSpace(appeal) + ? loc.GetString("ban-banned-permanent-appeal", ("link", appeal)) + : loc.GetString("ban-banned-permanent"); + } + + return $""" + {loc.GetString("ban-banned-1")} + {loc.GetString("ban-banned-2", ("reason", Reason))} + {expires} + {loc.GetString("ban-banned-3")} + """; + } + } +} diff --git a/Content.Server/Database/BanMatcher.cs b/Content.Server/Database/BanMatcher.cs index f477ccd822a..0302c0dc13a 100644 --- a/Content.Server/Database/BanMatcher.cs +++ b/Content.Server/Database/BanMatcher.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Linq; using System.Net; using Content.Server.IP; using Content.Shared.Database; @@ -7,7 +8,7 @@ namespace Content.Server.Database; /// -/// Implements logic to match a against a player query. +/// Implements logic to match a against a player query. /// /// /// @@ -29,7 +30,7 @@ public static class BanMatcher /// The ban information. /// Information about the player to match against. /// True if the ban matches the provided player info. - public static bool BanMatches(ServerBanDef ban, in PlayerInfo player) + public static bool BanMatches(BanDef ban, in PlayerInfo player) { var exemptFlags = player.ExemptFlags; // Any flag to bypass BlacklistedRange bans. @@ -39,39 +40,44 @@ public static bool BanMatches(ServerBanDef ban, in PlayerInfo player) if ((ban.ExemptFlags & exemptFlags) != 0) return false; + var playerAddr = player.Address; if (!player.ExemptFlags.HasFlag(ServerBanExemptFlags.IP) - && player.Address != null - && ban.Address is not null - && player.Address.IsInSubnet(ban.Address.Value) + && playerAddr != null + && ban.Addresses.Any(addr => playerAddr.IsInSubnet(addr)) && (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || player.IsNewPlayer)) { return true; } - if (player.UserId is { } id && ban.UserId == id.UserId) + if (player.UserId is { } id && ban.UserIds.Contains(id)) { return true; } - switch (ban.HWId?.Type) + foreach (var banHwid in ban.HWIds) { - case HwidType.Legacy: - if (player.HWId is { Length: > 0 } hwIdVar - && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan())) - { - return true; - } - break; - case HwidType.Modern: - if (player.ModernHWIds is { Length: > 0 } modernHwIdVar) - { - foreach (var hwid in modernHwIdVar) + switch (banHwid.Type) + { + case HwidType.Legacy: + if (player.HWId is { Length: > 0 } hwIdVar + && hwIdVar.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan())) { - if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan())) - return true; + return true; } - } - break; + + break; + case HwidType.Modern: + if (player.ModernHWIds is { Length: > 0 } modernHwIdVar) + { + foreach (var hwid in modernHwIdVar) + { + if (hwid.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan())) + return true; + } + } + + break; + } } return false; diff --git a/Content.Server/Database/DatabaseRecords.cs b/Content.Server/Database/DatabaseRecords.cs index 30fba3434b8..63ab45a726b 100644 --- a/Content.Server/Database/DatabaseRecords.cs +++ b/Content.Server/Database/DatabaseRecords.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Net; using Content.Shared.Database; using Robust.Shared.Network; @@ -12,9 +13,9 @@ public interface IAdminRemarksRecord { public int Id { get; } - public RoundRecord? Round { get; } + public ImmutableArray Rounds { get; } - public PlayerRecord? Player { get; } + public ImmutableArray Players { get; } public TimeSpan PlaytimeAtNote { get; } public string Message { get; } @@ -31,10 +32,11 @@ public interface IAdminRemarksRecord public bool Deleted { get; } } -public sealed record ServerRoleBanNoteRecord( +public sealed record BanNoteRecord( int Id, - RoundRecord? Round, - PlayerRecord? Player, + BanType Type, + ImmutableArray Rounds, + ImmutableArray Players, TimeSpan PlaytimeAtNote, string Message, NoteSeverity Severity, @@ -44,25 +46,9 @@ public sealed record ServerRoleBanNoteRecord( DateTimeOffset? LastEditedAt, DateTimeOffset? ExpirationTime, bool Deleted, - string[] Roles, PlayerRecord? UnbanningAdmin, - DateTime? UnbanTime) : IAdminRemarksRecord; - -public sealed record ServerBanNoteRecord( - int Id, - RoundRecord? Round, - PlayerRecord? Player, - TimeSpan PlaytimeAtNote, - string Message, - NoteSeverity Severity, - PlayerRecord? CreatedBy, - DateTimeOffset CreatedAt, - PlayerRecord? LastEditedBy, - DateTimeOffset? LastEditedAt, - DateTimeOffset? ExpirationTime, - bool Deleted, - PlayerRecord? UnbanningAdmin, - DateTime? UnbanTime) : IAdminRemarksRecord; + DateTime? UnbanTime, + ImmutableArray Roles) : IAdminRemarksRecord; public sealed record AdminNoteRecord( int Id, @@ -79,7 +65,11 @@ public sealed record AdminNoteRecord( bool Deleted, PlayerRecord? DeletedBy, DateTimeOffset? DeletedAt, - bool Secret) : IAdminRemarksRecord; + bool Secret) : IAdminRemarksRecord +{ + ImmutableArray IAdminRemarksRecord.Rounds => Round != null ? [Round] : []; + ImmutableArray IAdminRemarksRecord.Players => Player != null ? [Player] : []; +} public sealed record AdminWatchlistRecord( int Id, @@ -94,7 +84,11 @@ public sealed record AdminWatchlistRecord( DateTimeOffset? ExpirationTime, bool Deleted, PlayerRecord? DeletedBy, - DateTimeOffset? DeletedAt) : IAdminRemarksRecord; + DateTimeOffset? DeletedAt) : IAdminRemarksRecord +{ + ImmutableArray IAdminRemarksRecord.Rounds => Round != null ? [Round] : []; + ImmutableArray IAdminRemarksRecord.Players => Player != null ? [Player] : []; +} public sealed record AdminMessageRecord( int Id, @@ -111,15 +105,18 @@ public sealed record AdminMessageRecord( PlayerRecord? DeletedBy, DateTimeOffset? DeletedAt, bool Seen, - bool Dismissed) : IAdminRemarksRecord; - + bool Dismissed) : IAdminRemarksRecord +{ + ImmutableArray IAdminRemarksRecord.Rounds => Round != null ? [Round] : []; + ImmutableArray IAdminRemarksRecord.Players => Player != null ? [Player] : []; +} public sealed record PlayerRecord( NetUserId UserId, DateTimeOffset FirstSeenTime, string LastSeenUserName, DateTimeOffset LastSeenTime, - IPAddress LastSeenAddress, + IPAddress? LastSeenAddress, ImmutableTypedHwid? HWId); public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server); diff --git a/Content.Server/Database/EFCoreExtensions.cs b/Content.Server/Database/EFCoreExtensions.cs new file mode 100644 index 00000000000..58dbc4639b2 --- /dev/null +++ b/Content.Server/Database/EFCoreExtensions.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Content.Server.Database; + +internal static class EFCoreExtensions +{ + extension(IQueryable query) where TEntity : class + { + public IQueryable ApplyIncludes( + IEnumerable>> properties) + { + var q = query; + foreach (var property in properties) + { + q = q.Include(property); + } + + return q; + } + + public IQueryable ApplyIncludes( + IEnumerable>> properties, + Expression> getDerived) + where TDerived : class + { + var q = query; + foreach (var property in properties) + { + q = q.Include(getDerived).ThenInclude(property); + } + + return q; + } + } +} diff --git a/Content.Server/Database/ServerBanDef.cs b/Content.Server/Database/ServerBanDef.cs deleted file mode 100644 index eb03227e1ea..00000000000 --- a/Content.Server/Database/ServerBanDef.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Net; -using Content.Shared.CCVar; -using Content.Shared.Database; -using Robust.Shared.Configuration; -using Robust.Shared.Network; - - -namespace Content.Server.Database -{ - public sealed class ServerBanDef - { - public int? Id { get; } - public NetUserId? UserId { get; } - public (IPAddress address, int cidrMask)? Address { get; } - public ImmutableTypedHwid? HWId { get; } - - public DateTimeOffset BanTime { get; } - public DateTimeOffset? ExpirationTime { get; } - public int? RoundId { get; } - public TimeSpan PlaytimeAtNote { get; } - public string Reason { get; } - public NoteSeverity Severity { get; set; } - public NetUserId? BanningAdmin { get; } - public ServerUnbanDef? Unban { get; } - public ServerBanExemptFlags ExemptFlags { get; } - - public ServerBanDef(int? id, - NetUserId? userId, - (IPAddress, int)? address, - TypedHwid? hwId, - DateTimeOffset banTime, - DateTimeOffset? expirationTime, - int? roundId, - TimeSpan playtimeAtNote, - string reason, - NoteSeverity severity, - NetUserId? banningAdmin, - ServerUnbanDef? unban, - ServerBanExemptFlags exemptFlags = default) - { - if (userId == null && address == null && hwId == null) - { - throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID"); - } - - if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6) - { - // Fix IPv6-mapped IPv4 addresses - // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. - address = (addr.Item1.MapToIPv4(), addr.Item2 - 96); - } - - Id = id; - UserId = userId; - Address = address; - HWId = hwId; - BanTime = banTime; - ExpirationTime = expirationTime; - RoundId = roundId; - PlaytimeAtNote = playtimeAtNote; - Reason = reason; - Severity = severity; - BanningAdmin = banningAdmin; - Unban = unban; - ExemptFlags = exemptFlags; - } - - public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc) - { - string expires; - if (ExpirationTime is { } expireTime) - { - var duration = expireTime - BanTime; - var utc = expireTime.ToUniversalTime(); - expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f"))); - } - else - { - var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal); - expires = !string.IsNullOrWhiteSpace(appeal) - ? loc.GetString("ban-banned-permanent-appeal", ("link", appeal)) - : loc.GetString("ban-banned-permanent"); - } - - return $""" - {loc.GetString("ban-banned-1")} - {loc.GetString("ban-banned-2", ("reason", Reason))} - {expires} - {loc.GetString("ban-banned-3")} - {loc.GetString("ban-banned-4")} - """; - } - } -} diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 977ef0e4c86..dadff836e51 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Linq.Expressions; using System.Net; using System.Net.Http; // DS14 playtimeserver using System.Net.Http.Json; // DS14 playtimeserver @@ -9,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.Administration.Logs; -using Content.Server.Administration.Managers; using Content.Shared.Administration.Logs; using Content.Shared.Construction.Prototypes; using Content.Shared.Database; @@ -476,7 +476,7 @@ public async Task AssignUserIdAsync(string name, NetUserId netUserId) /// /// The ban id to look for. /// The ban with the given id or null if none exist. - public abstract Task GetServerBanAsync(int id); + public abstract Task GetBanAsync(int id); /// /// Looks up an user's most recent received un-pardoned ban. @@ -488,11 +488,12 @@ public async Task AssignUserIdAsync(string name, NetUserId netUserId) /// The legacy HWId of the user. /// The modern HWIDs of the user. /// The user's latest received un-pardoned ban, or null if none exist. - public abstract Task GetServerBanAsync( + public abstract Task GetBanAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - ImmutableArray>? modernHWIds); + ImmutableArray>? modernHWIds, + BanType type); /// /// Looks up an user's ban history. @@ -505,17 +506,18 @@ public async Task AssignUserIdAsync(string name, NetUserId netUserId) /// The modern HWIDs of the user. /// Include pardoned and expired bans. /// The user's ban history. - public abstract Task> GetServerBansAsync( + public abstract Task> GetBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned); + bool includeUnbanned, + BanType type); - public abstract Task AddServerBanAsync(ServerBanDef serverBan); - public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban); + public abstract Task AddBanAsync(BanDef ban); + public abstract Task AddUnbanAsync(UnbanDef unban); - public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) + public async Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) { await using var db = await GetDb(); @@ -578,61 +580,23 @@ public async Task GetBanExemption(NetUserId userId, Cancel return flags ?? ServerBanExemptFlags.None; } - #endregion - - #region Role Bans - /* - * ROLE BANS - */ - /// - /// Looks up a role ban by id. - /// This will return a pardoned role ban as well. - /// - /// The role ban id to look for. - /// The role ban with the given id or null if none exist. - public abstract Task GetServerRoleBanAsync(int id); - - /// - /// Looks up an user's role ban history. - /// This will return pardoned role bans based on the bool. - /// Requires one of , , or to not be null. - /// - /// The IP address of the user. - /// The NetUserId of the user. - /// The Hardware Id of the user. - /// The modern HWIDs of the user. - /// Whether expired and pardoned bans are included. - /// The user's role ban history. - public abstract Task> GetServerRoleBansAsync(IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - bool includeUnbanned); - - public abstract Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan); - public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban); - - public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) + protected static List>> GetBanDefIncludes(BanType? type = null) { - await using var db = await GetDb(); - var roleBanDetails = await db.DbContext.RoleBan - .Where(b => b.Id == id) - .Select(b => new { b.BanTime, b.PlayerUserId }) - .SingleOrDefaultAsync(); + List>> list = + [ + b => b.Players!, + b => b.Rounds!, + b => b.Hwids!, + b => b.Unban!, + b => b.Addresses!, + ]; - if (roleBanDetails == default) - return; + if (type != BanType.Server) + list.Add(b => b.Roles!); - await db.DbContext.RoleBan - .Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId) - .ExecuteUpdateAsync(setters => setters - .SetProperty(b => b.Severity, severity) - .SetProperty(b => b.Reason, reason) - .SetProperty(b => b.ExpirationTime, expiration.HasValue ? expiration.Value.UtcDateTime : (DateTime?)null) - .SetProperty(b => b.LastEditedById, editedBy) - .SetProperty(b => b.LastEditedAt, editedAt.UtcDateTime) - ); + return list; } + #endregion #region Playtime @@ -767,6 +731,19 @@ protected async Task PlayerRecordExists(DbGuard db, NetUserId userId) if (player == null) return null; + return MakePlayerRecord(player.UserId, player); + } + + protected PlayerRecord MakePlayerRecord(Guid userId, Player? player) + { + if (player == null) + { + // We don't have a record for this player in the database. + // This is possible, for example, when banning people that never connected to the server. + // Just return fallback data here, I guess. + return new PlayerRecord(new NetUserId(userId), default, userId.ToString(), default, null, null); + } + return new PlayerRecord( new NetUserId(player.UserId), new DateTimeOffset(NormalizeDatabaseTime(player.FirstSeenTime)), @@ -790,7 +767,7 @@ public abstract Task AddConnectionLogAsync(NetUserId userId, ConnectionDenyReason? denied, int serverId); - public async Task AddServerBanHitsAsync(int connection, IEnumerable bans) + public async Task AddServerBanHitsAsync(int connection, IEnumerable bans) { await using var db = await GetDb(); @@ -1404,81 +1381,17 @@ private AdminMessageRecord MakeAdminMessageRecord(AdminMessage entity) entity.Dismissed); } - public async Task GetServerBanAsNoteAsync(int id) + public async Task GetBanAsNoteAsync(int id) { await using var db = await GetDb(); - var ban = await db.DbContext.Ban - .Include(ban => ban.Unban) - .Include(ban => ban.Round) - .ThenInclude(r => r!.Server) - .Include(ban => ban.CreatedBy) - .Include(ban => ban.LastEditedBy) - .Include(ban => ban.Unban) + var ban = await BanRecordQuery(db.DbContext) .SingleOrDefaultAsync(b => b.Id == id); if (ban is null) return null; - var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId); - return new ServerBanNoteRecord( - ban.Id, - MakeRoundRecord(ban.Round), - MakePlayerRecord(player), - ban.PlaytimeAtNote, - ban.Reason, - ban.Severity, - MakePlayerRecord(ban.CreatedBy), - ban.BanTime, - MakePlayerRecord(ban.LastEditedBy), - ban.LastEditedAt, - ban.ExpirationTime, - ban.Hidden, - MakePlayerRecord(ban.Unban?.UnbanningAdmin == null - ? null - : await db.DbContext.Player.SingleOrDefaultAsync(p => - p.UserId == ban.Unban.UnbanningAdmin.Value)), - ban.Unban?.UnbanTime); - } - - public async Task GetServerRoleBanAsNoteAsync(int id) - { - await using var db = await GetDb(); - - var ban = await db.DbContext.RoleBan - .Include(ban => ban.Unban) - .Include(ban => ban.Round) - .ThenInclude(r => r!.Server) - .Include(ban => ban.CreatedBy) - .Include(ban => ban.LastEditedBy) - .Include(ban => ban.Unban) - .SingleOrDefaultAsync(b => b.Id == id); - - if (ban is null) - return null; - - var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId); - var unbanningAdmin = - ban.Unban is null - ? null - : await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin); - - return new ServerRoleBanNoteRecord( - ban.Id, - MakeRoundRecord(ban.Round), - MakePlayerRecord(player), - ban.PlaytimeAtNote, - ban.Reason, - ban.Severity, - MakePlayerRecord(ban.CreatedBy), - ban.BanTime, - MakePlayerRecord(ban.LastEditedBy), - ban.LastEditedAt, - ban.ExpirationTime, - ban.Hidden, - new [] { ban.RoleId.Replace(BanManager.JobPrefix, null) }, - MakePlayerRecord(unbanningAdmin), - ban.Unban?.UnbanTime); + return await MakeBanNoteRecord(db.DbContext, ban); } public async Task> GetAllAdminRemarks(Guid player) @@ -1499,8 +1412,7 @@ public async Task> GetAllAdminRemarks(Guid player) .ToListAsync()).Select(MakeAdminNoteRecord)); notes.AddRange(await GetActiveWatchlistsImpl(db, player)); notes.AddRange(await GetMessagesImpl(db, player)); - notes.AddRange(await GetServerBansAsNotesForUser(db, player)); - notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player)); + notes.AddRange(await GetBansAsNotesForUser(db, player)); return notes; } public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime) @@ -1583,7 +1495,7 @@ public async Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset dele await db.DbContext.SaveChangesAsync(); } - public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) + public async Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) { await using var db = await GetDb(); @@ -1596,19 +1508,6 @@ public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset await db.DbContext.SaveChangesAsync(); } - public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) - { - await using var db = await GetDb(); - - var roleBan = await db.DbContext.RoleBan.Where(roleBan => roleBan.Id == id).SingleAsync(); - - roleBan.Hidden = true; - roleBan.LastEditedById = deletedBy; - roleBan.LastEditedAt = deletedAt.UtcDateTime; - - await db.DbContext.SaveChangesAsync(); - } - public async Task> GetVisibleAdminRemarks(Guid player) { await using var db = await GetDb(); @@ -1626,8 +1525,7 @@ public async Task> GetVisibleAdminRemarks(Guid player) .Include(note => note.Player) .ToListAsync()).Select(MakeAdminNoteRecord)); notesCol.AddRange(await GetMessagesImpl(db, player)); - notesCol.AddRange(await GetServerBansAsNotesForUser(db, player)); - notesCol.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player)); + notesCol.AddRange(await GetBansAsNotesForUser(db, player)); return notesCol; } @@ -1690,98 +1588,70 @@ public async Task MarkMessageAsSeen(int id, bool dismissedToo) await db.DbContext.SaveChangesAsync(); } - // These two are here because they get converted into notes later - protected async Task> GetServerBansAsNotesForUser(DbGuard db, Guid user) + private static IQueryable BanRecordQuery(ServerDbContext dbContext) { - // You can't group queries, as player will not always exist. When it doesn't, the - // whole query returns nothing - var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user); - var bans = await db.DbContext.Ban - .Where(ban => ban.PlayerUserId == user && !ban.Hidden) + return dbContext.Ban .Include(ban => ban.Unban) - .Include(ban => ban.Round) + .Include(ban => ban.Rounds!) + .ThenInclude(r => r.Round) .ThenInclude(r => r!.Server) + .Include(ban => ban.Addresses) + .Include(ban => ban.Players) + .Include(ban => ban.Roles) + .Include(ban => ban.Hwids) .Include(ban => ban.CreatedBy) .Include(ban => ban.LastEditedBy) - .Include(ban => ban.Unban) - .ToArrayAsync(); - - var banNotes = new List(); - foreach (var ban in bans) - { - var banNote = new ServerBanNoteRecord( - ban.Id, - MakeRoundRecord(ban.Round), - MakePlayerRecord(player), - ban.PlaytimeAtNote, - ban.Reason, - ban.Severity, - MakePlayerRecord(ban.CreatedBy), - NormalizeDatabaseTime(ban.BanTime), - MakePlayerRecord(ban.LastEditedBy), - NormalizeDatabaseTime(ban.LastEditedAt), - NormalizeDatabaseTime(ban.ExpirationTime), - ban.Hidden, - MakePlayerRecord(ban.Unban?.UnbanningAdmin == null - ? null - : await db.DbContext.Player.SingleOrDefaultAsync( - p => p.UserId == ban.Unban.UnbanningAdmin.Value)), - NormalizeDatabaseTime(ban.Unban?.UnbanTime)); + .Include(ban => ban.Unban); + } - banNotes.Add(banNote); - } + private async Task MakeBanNoteRecord(ServerDbContext dbContext, Ban ban) + { + var playerRecords = await AsyncSelect(ban.Players, + async bp => MakePlayerRecord(bp.UserId, + await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == bp.UserId))); - return banNotes; + return new BanNoteRecord( + ban.Id, + ban.Type, + [..ban.Rounds!.Select(br => MakeRoundRecord(br.Round!))], + [..playerRecords], + ban.PlaytimeAtNote, + ban.Reason, + ban.Severity, + MakePlayerRecord(ban.CreatedBy!), + NormalizeDatabaseTime(ban.BanTime), + MakePlayerRecord(ban.LastEditedBy!), + NormalizeDatabaseTime(ban.LastEditedAt), + NormalizeDatabaseTime(ban.ExpirationTime), + ban.Hidden, + ban.Unban?.UnbanningAdmin == null + ? null + : MakePlayerRecord( + ban.Unban.UnbanningAdmin.Value, + await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.Unban.UnbanningAdmin.Value)), + NormalizeDatabaseTime(ban.Unban?.UnbanTime), + [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))]); } - protected async Task> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user) + // These two are here because they get converted into notes later + protected async Task> GetBansAsNotesForUser(DbGuard db, Guid user) { - // Server side query - var bansQuery = await db.DbContext.RoleBan - .Where(ban => ban.PlayerUserId == user && !ban.Hidden) - .Include(ban => ban.Unban) - .Include(ban => ban.Round) - .ThenInclude(r => r!.Server) - .Include(ban => ban.CreatedBy) - .Include(ban => ban.LastEditedBy) - .Include(ban => ban.Unban) + // You can't group queries, as player will not always exist. When it doesn't, the + // whole query returns nothing + var bans = await BanRecordQuery(db.DbContext) + .AsSplitQuery() + .Where(ban => ban.Players!.Any(bp => bp.UserId == user) && !ban.Hidden) .ToArrayAsync(); - // Client side query, as EF can't do groups yet - var bansEnumerable = bansQuery - .GroupBy(ban => new { ban.BanTime, CreatedBy = (Player?)ban.CreatedBy, ban.Reason, Unbanned = ban.Unban == null }) - .Select(banGroup => banGroup) - .ToArray(); - - List bans = new(); - var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user); - foreach (var banGroup in bansEnumerable) + var banNotes = new List(); + foreach (var ban in bans) { - var firstBan = banGroup.First(); - Player? unbanningAdmin = null; - - if (firstBan.Unban?.UnbanningAdmin is not null) - unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value); - - bans.Add(new ServerRoleBanNoteRecord( - firstBan.Id, - MakeRoundRecord(firstBan.Round), - MakePlayerRecord(player), - firstBan.PlaytimeAtNote, - firstBan.Reason, - firstBan.Severity, - MakePlayerRecord(firstBan.CreatedBy), - NormalizeDatabaseTime(firstBan.BanTime), - MakePlayerRecord(firstBan.LastEditedBy), - NormalizeDatabaseTime(firstBan.LastEditedAt), - NormalizeDatabaseTime(firstBan.ExpirationTime), - firstBan.Hidden, - banGroup.Select(ban => ban.RoleId.Replace(BanManager.JobPrefix, null)).ToArray(), - MakePlayerRecord(unbanningAdmin), - NormalizeDatabaseTime(firstBan.Unban?.UnbanTime))); + var banNote = await MakeBanNoteRecord(db.DbContext, ban); + + banNotes.Add(banNote); } - return bans; + return banNotes; } #endregion @@ -2084,5 +1954,19 @@ public virtual void Shutdown() { } + + private static async Task> AsyncSelect( + IEnumerable? enumerable, + Func> selector) + { + var results = new List(); + + foreach (var item in enumerable ?? []) + { + results.Add(await selector(item)); + } + + return [..results]; + } } } diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 22704f3aa37..18bc01cdf9d 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -70,7 +70,7 @@ Task InitPrefsAsync( /// /// The ban id to look for. /// The ban with the given id or null if none exist. - Task GetServerBanAsync(int id); + Task GetBanAsync(int id); /// /// Looks up an user's most recent received un-pardoned ban. @@ -82,11 +82,12 @@ Task InitPrefsAsync( /// The legacy HWID of the user. /// The modern HWIDs of the user. /// The user's latest received un-pardoned ban, or null if none exist. - Task GetServerBanAsync( + Task GetBanAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - ImmutableArray>? modernHWIds); + ImmutableArray>? modernHWIds, + BanType type = BanType.Server); /// /// Looks up an user's ban history. @@ -98,17 +99,18 @@ Task InitPrefsAsync( /// The modern HWIDs of the user. /// If true, bans that have been expired or pardoned are also included. /// The user's ban history. - Task> GetServerBansAsync( + Task> GetBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned=true); + bool includeUnbanned=true, + BanType type = BanType.Server); - Task AddServerBanAsync(ServerBanDef serverBan); - Task AddServerUnbanAsync(ServerUnbanDef serverBan); + Task AddBanAsync(BanDef ban); + Task AddUnbanAsync(UnbanDef ban); - public Task EditServerBan( + public Task EditBan( int id, string reason, NoteSeverity severity, @@ -134,45 +136,6 @@ public Task EditServerBan( #endregion - #region Role Bans - /// - /// Looks up a role ban by id. - /// This will return a pardoned role ban as well. - /// - /// The role ban id to look for. - /// The role ban with the given id or null if none exist. - Task GetServerRoleBanAsync(int id); - - /// - /// Looks up an user's role ban history. - /// This will return pardoned role bans based on the bool. - /// Requires one of , , or to not be null. - /// - /// The IP address of the user. - /// The NetUserId of the user. - /// The Hardware Id of the user. - /// The modern HWIDs of the user. - /// Whether expired and pardoned bans are included. - /// The user's role ban history. - Task> GetServerRoleBansAsync( - IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - bool includeUnbanned = true); - - Task AddServerRoleBanAsync(ServerRoleBanDef serverBan); - Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan); - - public Task EditServerRoleBan( - int id, - string reason, - NoteSeverity severity, - DateTimeOffset? expiration, - Guid editedBy, - DateTimeOffset editedAt); - #endregion - #region Playtime /// @@ -212,7 +175,7 @@ Task AddConnectionLogAsync( ConnectionDenyReason? denied, int serverId); - Task AddServerBanHitsAsync(int connection, IEnumerable bans); + Task AddServerBanHitsAsync(int connection, IEnumerable bans); #endregion @@ -304,8 +267,7 @@ Task AddConnectionLogAsync( Task GetAdminNote(int id); Task GetAdminWatchlist(int id); Task GetAdminMessage(int id); - Task GetServerBanAsNoteAsync(int id); - Task GetServerRoleBanAsNoteAsync(int id); + Task GetBanAsNoteAsync(int id); Task> GetAllAdminRemarks(Guid player); Task> GetVisibleAdminNotes(Guid player); Task> GetActiveWatchlists(Guid player); @@ -316,8 +278,7 @@ Task AddConnectionLogAsync( Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt); Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt); Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt); - Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt); - Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt); + Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt); /// /// Mark an admin message as being seen by the target player. @@ -550,49 +511,51 @@ public Task AssignUserIdAsync(string name, NetUserId userId) return RunDbCommand(() => _db.GetAssignedUserIdAsync(name)); } - public Task GetServerBanAsync(int id) + public Task GetBanAsync(int id) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBanAsync(id)); + return RunDbCommand(() => _db.GetBanAsync(id)); } - public Task GetServerBanAsync( + public Task GetBanAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - ImmutableArray>? modernHWIds) + ImmutableArray>? modernHWIds, + BanType type = BanType.Server) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds)); + return RunDbCommand(() => _db.GetBanAsync(address, userId, hwId, modernHWIds, type)); } - public Task> GetServerBansAsync( + public Task> GetBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned=true) + bool includeUnbanned=true, + BanType type = BanType.Server) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned)); + return RunDbCommand(() => _db.GetBansAsync(address, userId, hwId, modernHWIds, includeUnbanned, type)); } - public Task AddServerBanAsync(ServerBanDef serverBan) + public Task AddBanAsync(BanDef ban) { DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.AddServerBanAsync(serverBan)); + return RunDbCommand(() => _db.AddBanAsync(ban)); } - public Task AddServerUnbanAsync(ServerUnbanDef serverUnban) + public Task AddUnbanAsync(UnbanDef unban) { DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban)); + return RunDbCommand(() => _db.AddUnbanAsync(unban)); } - public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) + public Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) { DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt)); + return RunDbCommand(() => _db.EditBan(id, reason, severity, expiration, editedBy, editedAt)); } public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags) @@ -607,43 +570,6 @@ public Task GetBanExemption(NetUserId userId, Cancellation return RunDbCommand(() => _db.GetBanExemption(userId, cancel)); } - #region Role Ban - public Task GetServerRoleBanAsync(int id) - { - DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerRoleBanAsync(id)); - } - - public Task> GetServerRoleBansAsync( - IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - bool includeUnbanned = true) - { - DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned)); - } - - public Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan) - { - DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.AddServerRoleBanAsync(serverRoleBan)); - } - - public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban) - { - DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban)); - } - - public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt) - { - DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt)); - } - #endregion - #region Playtime public Task> GetPlayTimes(Guid player, CancellationToken cancel) @@ -695,7 +621,7 @@ public Task AddConnectionLogAsync( return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId)); } - public Task AddServerBanHitsAsync(int connection, IEnumerable bans) + public Task AddServerBanHitsAsync(int connection, IEnumerable bans) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.AddServerBanHitsAsync(connection, bans)); @@ -956,16 +882,10 @@ public Task AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtN return RunDbCommand(() => _db.GetAdminMessage(id)); } - public Task GetServerBanAsNoteAsync(int id) + public Task GetBanAsNoteAsync(int id) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id)); - } - - public Task GetServerRoleBanAsNoteAsync(int id) - { - DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id)); + return RunDbCommand(() => _db.GetBanAsNoteAsync(id)); } public Task> GetAllAdminRemarks(Guid player) @@ -1027,16 +947,10 @@ public Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt) return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt)); } - public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) - { - DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt)); - } - - public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) + public Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt) { DbWriteOpsMetric.Inc(); - return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt)); + return RunDbCommand(() => _db.HideBanFromNotes(id, deletedBy, deletedAt)); } public Task MarkMessageAsSeen(int id, bool dismissedToo) diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index d51f9e43532..5e596727b99 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -68,24 +68,26 @@ public ServerDbPostgres(DbContextOptions options, } #region Ban - public override async Task GetServerBanAsync(int id) + public override async Task GetBanAsync(int id) { await using var db = await GetDbImpl(); var query = db.PgDbContext.Ban - .Include(p => p.Unban) - .Where(p => p.Id == id); + .ApplyIncludes(GetBanDefIncludes()) + .Where(p => p.Id == id) + .AsSplitQuery(); var ban = await query.SingleOrDefaultAsync(); return ConvertBan(ban); } - public override async Task GetServerBanAsync( + public override async Task GetBanAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - ImmutableArray>? modernHWIds) + ImmutableArray>? modernHWIds, + BanType type) { if (address == null && userId == null && hwId == null) { @@ -96,7 +98,7 @@ public ServerDbPostgres(DbContextOptions options, var exempt = await GetBanExemptionCore(db, userId); var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value); - var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer) + var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer, type) .OrderByDescending(b => b.BanTime); var ban = await query.FirstOrDefaultAsync(); @@ -104,11 +106,12 @@ public ServerDbPostgres(DbContextOptions options, return ConvertBan(ban); } - public override async Task> GetServerBansAsync(IPAddress? address, + public override async Task> GetBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned) + bool includeUnbanned, + BanType type) { if (address == null && userId == null && hwId == null) { @@ -117,12 +120,11 @@ public override async Task> GetServerBansAsync(IPAddress? add await using var db = await GetDbImpl(); - var exempt = await GetBanExemptionCore(db, userId); + var exempt = type == BanType.Role ? null : await GetBanExemptionCore(db, userId); var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId); - var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer); - + var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer, type); var queryBans = await query.ToArrayAsync(); - var bans = new List(queryBans.Length); + var bans = new List(queryBans.Length); foreach (var ban in queryBans) { @@ -137,7 +139,8 @@ public override async Task> GetServerBansAsync(IPAddress? add return bans; } - private static IQueryable MakeBanLookupQuery( + // This has to return IDs instead of direct objects because otherwise all the includes are too complicated. + private static IQueryable MakeBanLookupQuery( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, @@ -145,308 +148,113 @@ private static IQueryable MakeBanLookupQuery( DbGuardImpl db, bool includeUnbanned, ServerBanExemptFlags? exemptFlags, - bool newPlayer) + bool newPlayer, + BanType type) { DebugTools.Assert(!(address == null && userId == null && hwId == null)); - var query = MakeBanLookupQualityShared( - userId, - hwId, - modernHWIds, - db.PgDbContext.Ban); - - if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)) - { - var newQ = db.PgDbContext.Ban - .Include(p => p.Unban) - .Where(b => b.Address != null - && EF.Functions.ContainsOrEqual(b.Address.Value, address) - && !(b.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) && !newPlayer)); - - query = query == null ? newQ : query.Union(newQ); - } - - DebugTools.Assert( - query != null, - "At least one filter item (IP/UserID/HWID) must have been given to make query not null."); - - if (!includeUnbanned) - { - query = query.Where(p => - p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)); - } - - if (exemptFlags is { } exempt) - { - if (exempt != ServerBanExemptFlags.None) - exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange - - query = query.Where(b => (b.ExemptFlags & exempt) == 0); - } - - return query.Distinct(); - } - - private static IQueryable? MakeBanLookupQualityShared( - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - DbSet set) - where TBan : class, IBanCommon - where TUnban : class, IUnbanCommon - { - IQueryable? query = null; + var selectorQueries = new List>(); if (userId is { } uid) - { - var newQ = set - .Include(p => p.Unban) - .Where(b => b.PlayerUserId == uid.UserId); - - query = query == null ? newQ : query.Union(newQ); - } + selectorQueries.Add(db.DbContext.BanPlayer.Where(b => b.UserId == uid.UserId)); if (hwId != null && hwId.Value.Length > 0) { - var newQ = set - .Include(p => p.Unban) - .Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray())); - - query = query == null ? newQ : query.Union(newQ); + selectorQueries.Add(db.DbContext.BanHwid.Where(bh => + bh.HWId!.Type == HwidType.Legacy && bh.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray()) + )); } if (modernHWIds != null) { foreach (var modernHwid in modernHWIds) { - var newQ = set - .Include(p => p.Unban) - .Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray())); - - query = query == null ? newQ : query.Union(newQ); + selectorQueries.Add(db.DbContext.BanHwid + .Where(b => b.HWId!.Type == HwidType.Modern + && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray()))); } } - return query; - } - - private static ServerBanDef? ConvertBan(ServerBan? ban) - { - if (ban == null) - { - return null; - } - - NetUserId? uid = null; - if (ban.PlayerUserId is {} guid) - { - uid = new NetUserId(guid); - } - - NetUserId? aUid = null; - if (ban.BanningAdmin is {} aGuid) + if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None) + .HasFlag(ServerBanExemptFlags.IP)) { - aUid = new NetUserId(aGuid); + selectorQueries.Add(db.PgDbContext.BanAddress + .Where(ba => EF.Functions.ContainsOrEqual(ba.Address, address) + && !(ba.Ban!.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) && + !newPlayer))); } - var unbanDef = ConvertUnban(ban.Unban); - - return new ServerBanDef( - ban.Id, - uid, - ban.Address.ToTuple(), - ban.HWId, - ban.BanTime, - ban.ExpirationTime, - ban.RoundId, - ban.PlaytimeAtNote, - ban.Reason, - ban.Severity, - aUid, - unbanDef, - ban.ExemptFlags); - } - - private static ServerUnbanDef? ConvertUnban(ServerUnban? unban) - { - if (unban == null) - { - return null; - } - - NetUserId? aUid = null; - if (unban.UnbanningAdmin is {} aGuid) - { - aUid = new NetUserId(aGuid); - } - - return new ServerUnbanDef( - unban.Id, - aUid, - unban.UnbanTime); - } - - public override async Task AddServerBanAsync(ServerBanDef serverBan) - { - await using var db = await GetDbImpl(); - - db.PgDbContext.Ban.Add(new ServerBan - { - Address = serverBan.Address.ToNpgsqlInet(), - HWId = serverBan.HWId, - Reason = serverBan.Reason, - Severity = serverBan.Severity, - BanningAdmin = serverBan.BanningAdmin?.UserId, - BanTime = serverBan.BanTime.UtcDateTime, - ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, - RoundId = serverBan.RoundId, - PlaytimeAtNote = serverBan.PlaytimeAtNote, - PlayerUserId = serverBan.UserId?.UserId, - ExemptFlags = serverBan.ExemptFlags - }); - - await db.PgDbContext.SaveChangesAsync(); - } - - public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban) - { - await using var db = await GetDbImpl(); - - db.PgDbContext.Unban.Add(new ServerUnban - { - BanId = serverUnban.BanId, - UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId, - UnbanTime = serverUnban.UnbanTime.UtcDateTime - }); - - await db.PgDbContext.SaveChangesAsync(); - } - #endregion - - #region Role Ban - public override async Task GetServerRoleBanAsync(int id) - { - await using var db = await GetDbImpl(); - - var query = db.PgDbContext.RoleBan - .Include(p => p.Unban) - .Where(p => p.Id == id); - - var ban = await query.SingleOrDefaultAsync(); - - return ConvertRoleBan(ban); - - } - - public override async Task> GetServerRoleBansAsync(IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - bool includeUnbanned) - { - if (address == null && userId == null && hwId == null) - { - throw new ArgumentException("Address, userId, and hwId cannot all be null"); - } - - await using var db = await GetDbImpl(); - - var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned) - .OrderByDescending(b => b.BanTime); + DebugTools.Assert( + selectorQueries.Count > 0, + "At least one filter item (IP/UserID/HWID) must have been given to make query not null."); - return await QueryRoleBans(query); - } + var selectorQuery = selectorQueries + .Select(q => q.Select(sel => sel.BanId)) + .Aggregate((selectors, queryable) => selectors.Union(queryable)); - private static async Task> QueryRoleBans(IQueryable query) - { - var queryRoleBans = await query.ToArrayAsync(); - var bans = new List(queryRoleBans.Length); + var banQuery = db.DbContext.Ban.Where(b => selectorQuery.Contains(b.Id)); - foreach (var ban in queryRoleBans) + if (!includeUnbanned) { - var banDef = ConvertRoleBan(ban); - - if (banDef != null) - { - bans.Add(banDef); - } + banQuery = banQuery.Where(p => + p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)); } - return bans; - } - - private static IQueryable MakeRoleBanLookupQuery( - IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - DbGuardImpl db, - bool includeUnbanned) - { - var query = MakeBanLookupQualityShared( - userId, - hwId, - modernHWIds, - db.PgDbContext.RoleBan); - - if (address != null) + if (exemptFlags is { } exempt) { - var newQ = db.PgDbContext.RoleBan - .Include(p => p.Unban) - .Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address)); + if (exempt != ServerBanExemptFlags.None) + exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange - query = query == null ? newQ : query.Union(newQ); + banQuery = banQuery.Where(b => (b.ExemptFlags & exempt) == 0); } - if (!includeUnbanned) - { - query = query?.Where(p => - p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)); - } - - query = query!.Distinct(); - return query; + return banQuery + .Where(b => b.Type == type) + .ApplyIncludes(GetBanDefIncludes(type)) + .AsSplitQuery(); } [return: NotNullIfNotNull(nameof(ban))] - private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban) + private static BanDef? ConvertBan(Ban? ban) { if (ban == null) { return null; } - NetUserId? uid = null; - if (ban.PlayerUserId is {} guid) - { - uid = new NetUserId(guid); - } - NetUserId? aUid = null; if (ban.BanningAdmin is {} aGuid) { aUid = new NetUserId(aGuid); } - var unbanDef = ConvertRoleUnban(ban.Unban); + var unbanDef = ConvertUnban(ban.Unban); - return new ServerRoleBanDef( + ImmutableArray? roles = null; + if (ban.Type == BanType.Role) + { + roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))]; + } + + return new BanDef( ban.Id, - uid, - ban.Address.ToTuple(), - ban.HWId, + ban.Type, + [..ban.Players!.Select(bp => new NetUserId(bp.UserId))], + [..ban.Addresses!.Select(ba => ba.Address.ToTuple())], + [..ban.Hwids!.Select(bh => bh.HWId)], ban.BanTime, ban.ExpirationTime, - ban.RoundId, + [..ban.Rounds!.Select(r => r.RoundId)], ban.PlaytimeAtNote, ban.Reason, ban.Severity, aUid, unbanDef, - ban.RoleId); + ban.ExemptFlags, + roles); } - private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban) + private static UnbanDef? ConvertUnban(Unban? unban) { if (unban == null) { @@ -459,45 +267,54 @@ private static IQueryable MakeRoleBanLookupQuery( aUid = new NetUserId(aGuid); } - return new ServerRoleUnbanDef( + return new UnbanDef( unban.Id, aUid, unban.UnbanTime); } - public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan) + public override async Task AddBanAsync(BanDef ban) { await using var db = await GetDbImpl(); - var ban = new ServerRoleBan - { - Address = serverRoleBan.Address.ToNpgsqlInet(), - HWId = serverRoleBan.HWId, - Reason = serverRoleBan.Reason, - Severity = serverRoleBan.Severity, - BanningAdmin = serverRoleBan.BanningAdmin?.UserId, - BanTime = serverRoleBan.BanTime.UtcDateTime, - ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime, - RoundId = serverRoleBan.RoundId, - PlaytimeAtNote = serverRoleBan.PlaytimeAtNote, - PlayerUserId = serverRoleBan.UserId?.UserId, - RoleId = serverRoleBan.Role, + var banEntity = new Ban + { + Type = ban.Type, + Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })], + Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })], + Reason = ban.Reason, + Severity = ban.Severity, + BanningAdmin = ban.BanningAdmin?.UserId, + BanTime = ban.BanTime.UtcDateTime, + ExpirationTime = ban.ExpirationTime?.UtcDateTime, + Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })], + PlaytimeAtNote = ban.PlaytimeAtNote, + Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })], + ExemptFlags = ban.ExemptFlags, + Roles = ban.Roles == null + ? [] + : ban.Roles.Value.Select(brd => new BanRole + { + RoleType = brd.RoleType, + RoleId = brd.RoleId + }) + .ToList(), }; - db.PgDbContext.RoleBan.Add(ban); + db.PgDbContext.Ban.Add(banEntity); await db.PgDbContext.SaveChangesAsync(); - return ConvertRoleBan(ban); + return ConvertBan(banEntity); } - public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban) + public override async Task AddUnbanAsync(UnbanDef unban) { await using var db = await GetDbImpl(); - db.PgDbContext.RoleUnban.Add(new ServerRoleUnban + db.PgDbContext.Unban.Add(new Unban { - BanId = serverRoleUnban.BanId, - UnbanningAdmin = serverRoleUnban.UnbanningAdmin?.UserId, - UnbanTime = serverRoleUnban.UnbanTime.UtcDateTime + BanId = unban.BanId, + UnbanningAdmin = unban.UnbanningAdmin?.UserId, + UnbanTime = unban.UnbanTime.UtcDateTime }); await db.PgDbContext.SaveChangesAsync(); diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index 5281ba1c90d..4f4a8471908 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -76,48 +76,52 @@ public ServerDbSqlite( } #region Ban - public override async Task GetServerBanAsync(int id) + public override async Task GetBanAsync(int id) { await using var db = await GetDbImpl(); var ban = await db.SqliteDbContext.Ban - .Include(p => p.Unban) + .ApplyIncludes(GetBanDefIncludes()) .Where(p => p.Id == id) + .AsSplitQuery() .SingleOrDefaultAsync(); return ConvertBan(ban); } - public override async Task GetServerBanAsync( + public override async Task GetBanAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - ImmutableArray>? modernHWIds) + ImmutableArray>? modernHWIds, + BanType type) { await using var db = await GetDbImpl(); - return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault(); + return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false, type)).FirstOrDefault(); } - public override async Task> GetServerBansAsync( + public override async Task> GetBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned) + bool includeUnbanned, + BanType type) { await using var db = await GetDbImpl(); - return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList(); + return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned, type)).ToList(); } - private async Task> GetServerBanQueryAsync( + private async Task> GetBanQueryAsync( DbGuardImpl db, IPAddress? address, NetUserId? userId, ImmutableArray? hwId, ImmutableArray>? modernHWIds, - bool includeUnbanned) + bool includeUnbanned, + BanType type) { var exempt = await GetBanExemptionCore(db, userId); @@ -125,7 +129,7 @@ private async Task> GetServerBanQueryAsync( // SQLite can't do the net masking stuff we need to match IP address ranges. // So just pull down the whole list into memory. - var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt); + var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt, type); var playerInfo = new BanMatcher.PlayerInfo { @@ -142,12 +146,12 @@ private async Task> GetServerBanQueryAsync( .Where(b => BanMatcher.BanMatches(b!, playerInfo))!; } - private static async Task> GetAllBans( - SqliteServerDbContext db, + private static async Task> GetAllBans(SqliteServerDbContext db, bool includeUnbanned, - ServerBanExemptFlags? exemptFlags) + ServerBanExemptFlags? exemptFlags, + BanType type) { - IQueryable query = db.Ban.Include(p => p.Unban); + var query = db.Ban.Where(b => b.Type == type).ApplyIncludes(GetBanDefIncludes(type)); if (!includeUnbanned) { query = query.Where(p => @@ -163,269 +167,99 @@ private static async Task> GetAllBans( query = query.Where(b => (b.ExemptFlags & exempt) == 0); } - return await query.ToListAsync(); - } - - public override async Task AddServerBanAsync(ServerBanDef serverBan) - { - await using var db = await GetDbImpl(); - - db.SqliteDbContext.Ban.Add(new ServerBan - { - Address = serverBan.Address.ToNpgsqlInet(), - Reason = serverBan.Reason, - Severity = serverBan.Severity, - BanningAdmin = serverBan.BanningAdmin?.UserId, - HWId = serverBan.HWId, - BanTime = serverBan.BanTime.UtcDateTime, - ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, - RoundId = serverBan.RoundId, - PlaytimeAtNote = serverBan.PlaytimeAtNote, - PlayerUserId = serverBan.UserId?.UserId, - ExemptFlags = serverBan.ExemptFlags - }); - - await db.SqliteDbContext.SaveChangesAsync(); + return await query.AsSplitQuery().ToListAsync(); } - public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban) - { - await using var db = await GetDbImpl(); - - db.SqliteDbContext.Unban.Add(new ServerUnban - { - BanId = serverUnban.BanId, - UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId, - UnbanTime = serverUnban.UnbanTime.UtcDateTime - }); - - await db.SqliteDbContext.SaveChangesAsync(); - } - #endregion - - #region Role Ban - public override async Task GetServerRoleBanAsync(int id) - { - await using var db = await GetDbImpl(); - - var ban = await db.SqliteDbContext.RoleBan - .Include(p => p.Unban) - .Where(p => p.Id == id) - .SingleOrDefaultAsync(); - - return ConvertRoleBan(ban); - } - - public override async Task> GetServerRoleBansAsync( - IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds, - bool includeUnbanned) + public override async Task AddBanAsync(BanDef ban) { await using var db = await GetDbImpl(); - // SQLite can't do the net masking stuff we need to match IP address ranges. - // So just pull down the whole list into memory. - var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned); - - return queryBans - .Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds)) - .Select(ConvertRoleBan) - .ToList()!; - } - - private static async Task> GetAllRoleBans( - SqliteServerDbContext db, - bool includeUnbanned) - { - IQueryable query = db.RoleBan.Include(p => p.Unban); - if (!includeUnbanned) - { - query = query.Where(p => - p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)); - } - - return await query.ToListAsync(); - } - - private static bool RoleBanMatches( - ServerRoleBan ban, - IPAddress? address, - NetUserId? userId, - ImmutableArray? hwId, - ImmutableArray>? modernHWIds) - { - if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value)) - { - return true; - } - - if (userId is { } id && ban.PlayerUserId == id.UserId) - { - return true; - } - - switch (ban.HWId?.Type) - { - case HwidType.Legacy: - if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid)) - return true; - break; - - case HwidType.Modern: - if (modernHWIds != null) - { - foreach (var modernHWId in modernHWIds) + var banEntity = new Ban + { + Type = ban.Type, + Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })], + Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })], + Reason = ban.Reason, + Severity = ban.Severity, + BanningAdmin = ban.BanningAdmin?.UserId, + BanTime = ban.BanTime.UtcDateTime, + ExpirationTime = ban.ExpirationTime?.UtcDateTime, + Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })], + PlaytimeAtNote = ban.PlaytimeAtNote, + Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })], + ExemptFlags = ban.ExemptFlags, + Roles = ban.Roles == null + ? [] + : ban.Roles.Value.Select(brd => new BanRole { - if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid)) - return true; - } - } - - break; - } - - return false; - } - - public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverBan) - { - await using var db = await GetDbImpl(); - - var ban = new ServerRoleBan - { - Address = serverBan.Address.ToNpgsqlInet(), - Reason = serverBan.Reason, - Severity = serverBan.Severity, - BanningAdmin = serverBan.BanningAdmin?.UserId, - HWId = serverBan.HWId, - BanTime = serverBan.BanTime.UtcDateTime, - ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, - RoundId = serverBan.RoundId, - PlaytimeAtNote = serverBan.PlaytimeAtNote, - PlayerUserId = serverBan.UserId?.UserId, - RoleId = serverBan.Role, + RoleType = brd.RoleType, + RoleId = brd.RoleId + }) + .ToList(), }; - db.SqliteDbContext.RoleBan.Add(ban); + db.SqliteDbContext.Ban.Add(banEntity); await db.SqliteDbContext.SaveChangesAsync(); - return ConvertRoleBan(ban); + return ConvertBan(banEntity); } - public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnban) + public override async Task AddUnbanAsync(UnbanDef unban) { await using var db = await GetDbImpl(); - db.SqliteDbContext.RoleUnban.Add(new ServerRoleUnban + db.SqliteDbContext.Unban.Add(new Unban { - BanId = serverUnban.BanId, - UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId, - UnbanTime = serverUnban.UnbanTime.UtcDateTime + BanId = unban.BanId, + UnbanningAdmin = unban.UnbanningAdmin?.UserId, + UnbanTime = unban.UnbanTime.UtcDateTime }); await db.SqliteDbContext.SaveChangesAsync(); } + #endregion [return: NotNullIfNotNull(nameof(ban))] - private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban) + private static BanDef? ConvertBan(Ban? ban) { if (ban == null) { return null; } - NetUserId? uid = null; - if (ban.PlayerUserId is { } guid) - { - uid = new NetUserId(guid); - } - NetUserId? aUid = null; if (ban.BanningAdmin is { } aGuid) { aUid = new NetUserId(aGuid); } - var unban = ConvertRoleUnban(ban.Unban); - - return new ServerRoleBanDef( - ban.Id, - uid, - ban.Address.ToTuple(), - ban.HWId, - // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. - DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc), - ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc), - ban.RoundId, - ban.PlaytimeAtNote, - ban.Reason, - ban.Severity, - aUid, - unban, - ban.RoleId); - } - - private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban) - { - if (unban == null) - { - return null; - } - - NetUserId? aUid = null; - if (unban.UnbanningAdmin is { } aGuid) - { - aUid = new NetUserId(aGuid); - } - - return new ServerRoleUnbanDef( - unban.Id, - aUid, - // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. - DateTime.SpecifyKind(unban.UnbanTime, DateTimeKind.Utc)); - } - #endregion - - [return: NotNullIfNotNull(nameof(ban))] - private static ServerBanDef? ConvertBan(ServerBan? ban) - { - if (ban == null) - { - return null; - } - - NetUserId? uid = null; - if (ban.PlayerUserId is { } guid) - { - uid = new NetUserId(guid); - } + var unban = ConvertUnban(ban.Unban); - NetUserId? aUid = null; - if (ban.BanningAdmin is { } aGuid) + ImmutableArray? roles = null; + if (ban.Type == BanType.Role) { - aUid = new NetUserId(aGuid); + roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))]; } - var unban = ConvertUnban(ban.Unban); - - return new ServerBanDef( + return new BanDef( ban.Id, - uid, - ban.Address.ToTuple(), - ban.HWId, + ban.Type, + [..ban.Players!.Select(bp => new NetUserId(bp.UserId))], + [..ban.Addresses!.Select(ba => ba.Address.ToTuple())], + [..ban.Hwids!.Select(bh => bh.HWId)], // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc), ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc), - ban.RoundId, + [..ban.Rounds!.Select(r => r.RoundId)], ban.PlaytimeAtNote, ban.Reason, ban.Severity, aUid, - unban); + unban, + ban.ExemptFlags, + roles); } - private static ServerUnbanDef? ConvertUnban(ServerUnban? unban) + private static UnbanDef? ConvertUnban(Unban? unban) { if (unban == null) { @@ -438,7 +272,7 @@ public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnba aUid = new NetUserId(aGuid); } - return new ServerUnbanDef( + return new UnbanDef( unban.Id, aUid, // SQLite apparently always reads DateTime as unspecified, but we always write as UTC. diff --git a/Content.Server/Database/ServerRoleBanDef.cs b/Content.Server/Database/ServerRoleBanDef.cs deleted file mode 100644 index dda3a822378..00000000000 --- a/Content.Server/Database/ServerRoleBanDef.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Net; -using Content.Shared.Database; -using Robust.Shared.Network; - -namespace Content.Server.Database; - -public sealed class ServerRoleBanDef -{ - public int? Id { get; } - public NetUserId? UserId { get; } - public (IPAddress address, int cidrMask)? Address { get; } - public ImmutableTypedHwid? HWId { get; } - - public DateTimeOffset BanTime { get; } - public DateTimeOffset? ExpirationTime { get; } - public int? RoundId { get; } - public TimeSpan PlaytimeAtNote { get; } - public string Reason { get; } - public NoteSeverity Severity { get; set; } - public NetUserId? BanningAdmin { get; } - public ServerRoleUnbanDef? Unban { get; } - public string Role { get; } - - public ServerRoleBanDef( - int? id, - NetUserId? userId, - (IPAddress, int)? address, - ImmutableTypedHwid? hwId, - DateTimeOffset banTime, - DateTimeOffset? expirationTime, - int? roundId, - TimeSpan playtimeAtNote, - string reason, - NoteSeverity severity, - NetUserId? banningAdmin, - ServerRoleUnbanDef? unban, - string role) - { - if (userId == null && address == null && hwId == null) - { - throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID"); - } - - if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6) - { - // Fix IPv6-mapped IPv4 addresses - // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. - address = (addr.Item1.MapToIPv4(), addr.Item2 - 96); - } - - Id = id; - UserId = userId; - Address = address; - HWId = hwId; - BanTime = banTime; - ExpirationTime = expirationTime; - RoundId = roundId; - PlaytimeAtNote = playtimeAtNote; - Reason = reason; - Severity = severity; - BanningAdmin = banningAdmin; - Unban = unban; - Role = role; - } -} diff --git a/Content.Server/Database/ServerRoleUnbanDef.cs b/Content.Server/Database/ServerRoleUnbanDef.cs deleted file mode 100644 index 3960a86808f..00000000000 --- a/Content.Server/Database/ServerRoleUnbanDef.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Robust.Shared.Network; - -namespace Content.Server.Database; - -public sealed class ServerRoleUnbanDef -{ - public int BanId { get; } - - public NetUserId? UnbanningAdmin { get; } - - public DateTimeOffset UnbanTime { get; } - - public ServerRoleUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime) - { - BanId = banId; - UnbanningAdmin = unbanningAdmin; - UnbanTime = unbanTime; - } -} diff --git a/Content.Server/Database/ServerUnbanDef.cs b/Content.Server/Database/UnbanDef.cs similarity index 72% rename from Content.Server/Database/ServerUnbanDef.cs rename to Content.Server/Database/UnbanDef.cs index 3d39a6b90c4..1fa4fc2a6a9 100644 --- a/Content.Server/Database/ServerUnbanDef.cs +++ b/Content.Server/Database/UnbanDef.cs @@ -2,7 +2,7 @@ namespace Content.Server.Database { - public sealed class ServerUnbanDef + public sealed class UnbanDef { public int BanId { get; } @@ -10,7 +10,7 @@ public sealed class ServerUnbanDef public DateTimeOffset UnbanTime { get; } - public ServerUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime) + public UnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime) { BanId = banId; UnbanningAdmin = unbanningAdmin; diff --git a/Content.Server/Destructible/Thresholds/Behaviors/EmptyAllContainersBehaviour.cs b/Content.Server/Destructible/Thresholds/Behaviors/EmptyAllContainersBehaviour.cs index 60e7423247b..9e66c521a17 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/EmptyAllContainersBehaviour.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/EmptyAllContainersBehaviour.cs @@ -15,7 +15,7 @@ public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause foreach (var container in system.EntityManager.System().GetAllContainers(owner, containerManager)) { - system.ContainerSystem.EmptyContainer(container, true, system.EntityManager.GetComponent(owner).Coordinates); + system.ContainerSystem.EmptyContainer(container, true); } } } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 9f042a16c29..4f4f9faa7d2 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -21,8 +21,6 @@ using Content.Server.IoC; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; -using Content.Server.Objectives; -using Content.Server.Players; using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Players.RateLimiting; @@ -38,6 +36,7 @@ using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -48,140 +47,182 @@ public sealed class EntryPoint : GameServer internal const string ConfigPresetsDir = "/ConfigPresets/"; private const string ConfigPresetsDirBuild = $"{ConfigPresetsDir}Build/"; - private EuiManager _euiManager = default!; - private IVoteManager _voteManager = default!; - private ServerUpdateManager _updateManager = default!; - private PlayTimeTrackingManager? _playTimeTracking; - private IEntitySystemManager? _sysMan; - private IServerDbManager? _dbManager; - private IWatchlistWebhookManager _watchlistWebhookManager = default!; - private IConnectionManager? _connectionManager; + [Dependency] private readonly CVarControlManager _cvarCtrl = default!; + [Dependency] private readonly ContentLocalizationManager _loc = default!; + [Dependency] private readonly ContentNetworkResourceManager _netResMan = default!; + [Dependency] private readonly DiscordChatLink _discordChatLink = default!; + [Dependency] private readonly DiscordLink _discordLink = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly GhostKickManager _ghostKick = default!; + [Dependency] private readonly IAdminManager _admin = default!; + [Dependency] private readonly IAdminLogManager _adminLog = default!; + [Dependency] private readonly IAfkManager _afk = default!; + [Dependency] private readonly IBanManager _ban = default!; + [Dependency] private readonly IChatManager _chatSan = default!; // NOTE: Check if this should be _chatManager or similar in new version, kept as per file context but watch out for interface mismatches if IChatManager isn't IChatSanitizationManager + [Dependency] private readonly IChatSanitizationManager _chat = default!; + [Dependency] private readonly ChatFilterManager _chatFilter = default!; // Lua + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IConnectionManager _connection = default!; + [Dependency] private readonly IEntitySystemManager _entSys = default!; + [Dependency] private readonly IGameMapManager _gameMap = default!; + [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly INodeGroupFactory _nodeFactory = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IResourceManager _res = default!; + [Dependency] private readonly IServerDbManager _dbManager = default!; + [Dependency] private readonly IServerPreferencesManager _preferences = default!; + [Dependency] private readonly IStatusHost _host = default!; + [Dependency] private readonly IVoteManager _voteManager = default!; + [Dependency] private readonly IWatchlistWebhookManager _watchlistWebhookManager = default!; + [Dependency] private readonly JobWhitelistManager _job = default!; + [Dependency] private readonly MultiServerKickManager _multiServerKick = default!; + [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; + [Dependency] private readonly PlayerRateLimitManager _rateLimit = default!; + [Dependency] private readonly RecipeManager _recipe = default!; + [Dependency] private readonly RulesManager _rules = default!; + [Dependency] private readonly ServerApi _serverApi = default!; + [Dependency] private readonly ServerInfoManager _serverInfo = default!; + [Dependency] private readonly ServerUpdateManager _updateManager = default!; + // Custom dependencies + [Dependency] private readonly DiscordAuthManager _discordAuth = default!; // Corvax-DiscordAuth + [Dependency] private readonly JoinQueueManager _joinQueue = default!; // Corvax-Queue + [Dependency] private readonly TTSManager _tts = default!; // Corvax-TTS + [Dependency] private readonly MiniAuthManager _miniAuth = default!; // _NF.Auth + + public override void PreInit() + { + ServerContentIoC.Register(Dependencies); + foreach (var callback in TestingCallbacks) + { + var cast = (ServerModuleTestingCallbacks)callback; + cast.ServerBeforeIoC?.Invoke(); + } + + Dependencies.Resolve().FloatFlags = SerializerFloatFlags.RemoveReadNan; + } /// public override void Init() { base.Init(); + Dependencies.BuildGraph(); + Dependencies.InjectDependencies(this); - var cfg = IoCManager.Resolve(); - var res = IoCManager.Resolve(); - var logManager = IoCManager.Resolve(); - - LoadConfigPresets(cfg, res, logManager.GetSawmill("configpreset")); - - var aczProvider = new ContentMagicAczProvider(IoCManager.Resolve()); - IoCManager.Resolve().SetMagicAczProvider(aczProvider); + LoadConfigPresets(_cfg, _res, _log.GetSawmill("configpreset")); - var factory = IoCManager.Resolve(); - var prototypes = IoCManager.Resolve(); + var aczProvider = new ContentMagicAczProvider(Dependencies); + _host.SetMagicAczProvider(aczProvider); - factory.DoAutoRegistrations(); - factory.IgnoreMissingComponents("Visuals"); + _factory.DoAutoRegistrations(); + _factory.IgnoreMissingComponents("Visuals"); + _factory.RegisterIgnore(IgnoredComponents.List); + _factory.GenerateNetIds(); - factory.RegisterIgnore(IgnoredComponents.List); + _proto.RegisterIgnore("parallax"); - prototypes.RegisterIgnore("parallax"); + _loc.Initialize(); - ServerContentIoC.Register(); - - foreach (var callback in TestingCallbacks) - { - var cast = (ServerModuleTestingCallbacks) callback; - cast.ServerBeforeIoC?.Invoke(); - } - - IoCManager.BuildGraph(); - factory.GenerateNetIds(); - var configManager = IoCManager.Resolve(); - var dest = configManager.GetCVar(CCVars.DestinationFile); - IoCManager.Resolve().Initialize(); - if (string.IsNullOrEmpty(dest)) //hacky but it keeps load times for the generator down. + var dest = _cfg.GetCVar(CCVars.DestinationFile); + if (!string.IsNullOrEmpty(dest)) { - _euiManager = IoCManager.Resolve(); - _voteManager = IoCManager.Resolve(); - _updateManager = IoCManager.Resolve(); - _playTimeTracking = IoCManager.Resolve(); - _connectionManager = IoCManager.Resolve(); - _sysMan = IoCManager.Resolve(); - _dbManager = IoCManager.Resolve(); - _watchlistWebhookManager = IoCManager.Resolve(); + // hacky but it keeps load times for the generator down. + _log.GetSawmill("Storage").Level = LogLevel.Info; + _log.GetSawmill("db.ef").Level = LogLevel.Info; - logManager.GetSawmill("Storage").Level = LogLevel.Info; - logManager.GetSawmill("db.ef").Level = LogLevel.Info; - - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); + // Initialize bare minimum for data generation + _adminLog.Initialize(); + _connection.Initialize(); _dbManager.Init(); - IoCManager.Resolve().Init(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); // Corvax-DiscordAuth - IoCManager.Resolve().Initialize(); // Corvax-Queue - IoCManager.Resolve().Initialize(); // Corvax-TTS - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve(); - + _preferences.Init(); + _nodeFactory.Initialize(); + _netResMan.Initialize(); + _ghostKick.Initialize(); + _discordAuth.Initialize(); // Corvax-DiscordAuth + _joinQueue.Initialize(); // Corvax-Queue + _tts.Initialize(); // Corvax-TTS + _serverInfo.Initialize(); + _serverApi.Initialize(); _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); _watchlistWebhookManager.Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); + _job.Initialize(); + _rateLimit.Initialize(); + return; } + + _log.GetSawmill("Storage").Level = LogLevel.Info; + _log.GetSawmill("db.ef").Level = LogLevel.Info; + + _adminLog.Initialize(); + _connection.Initialize(); + _dbManager.Init(); + _preferences.Init(); + _nodeFactory.Initialize(); + _netResMan.Initialize(); + _ghostKick.Initialize(); + _discordAuth.Initialize(); // Corvax-DiscordAuth + _joinQueue.Initialize(); // Corvax-Queue + _tts.Initialize(); // Corvax-TTS + _serverInfo.Initialize(); + _serverApi.Initialize(); + _voteManager.Initialize(); + _updateManager.Initialize(); + _playTimeTracking.Initialize(); + _watchlistWebhookManager.Initialize(); + _job.Initialize(); + _rateLimit.Initialize(); } public override void PostInit() { base.PostInit(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); // Lua - IoCManager.Resolve().Initialize(); - var configManager = IoCManager.Resolve(); - var resourceManager = IoCManager.Resolve(); - var dest = configManager.GetCVar(CCVars.DestinationFile); + _chatSan.Initialize(); + _chat.Initialize(); + _chatFilter.Initialize(); // Lua + + var dest = _cfg.GetCVar(CCVars.DestinationFile); if (!string.IsNullOrEmpty(dest)) { var resPath = new ResPath(dest).ToRootedPath(); - var file = resourceManager.UserData.OpenWriteText(resPath.WithName("chem_" + dest)); + var file = _res.UserData.OpenWriteText(resPath.WithName("chem_" + dest)); ChemistryJsonGenerator.PublishJson(file); file.Flush(); - file = resourceManager.UserData.OpenWriteText(resPath.WithName("react_" + dest)); + file = _res.UserData.OpenWriteText(resPath.WithName("react_" + dest)); ReactionJsonGenerator.PublishJson(file); file.Flush(); + // Corvax-Wiki-Start - file = resourceManager.UserData.OpenWriteText(resPath.WithName("entity_" + dest)); + file = _res.UserData.OpenWriteText(resPath.WithName("entity_" + dest)); EntityJsonGenerator.PublishJson(file); file.Flush(); - file = resourceManager.UserData.OpenWriteText(resPath.WithName("mealrecipes_" + dest)); + file = _res.UserData.OpenWriteText(resPath.WithName("mealrecipes_" + dest)); MealsRecipesJsonGenerator.PublishJson(file); file.Flush(); - file = resourceManager.UserData.OpenWriteText(resPath.WithName("healthchangereagents_" + dest)); + file = _res.UserData.OpenWriteText(resPath.WithName("healthchangereagents_" + dest)); HealthChangeReagentsJsonGenerator.PublishJson(file); file.Flush(); // Corvax-Wiki-End - IoCManager.Resolve().Shutdown("Data generation done"); - } - else - { - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - _euiManager.Initialize(); - - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().GetEntitySystem().PostInitialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().PostInit(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); + Dependencies.Resolve().Shutdown("Data generation done"); + return; } + + _recipe.Initialize(); + _admin.Initialize(); + _afk.Initialize(); + _rules.Initialize(); + _discordLink.Initialize(); + _discordChatLink.Initialize(); + _euiManager.Initialize(); + _gameMap.Initialize(); + _entSys.GetEntitySystem().PostInitialize(); + _ban.Initialize(); + _connection.PostInit(); + _multiServerKick.Initialize(); + _cvarCtrl.Initialize(); } public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) @@ -191,30 +232,33 @@ public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) switch (level) { case ModUpdateLevel.PostEngine: - { - _euiManager.SendUpdates(); - _voteManager.Update(); - break; - } + { + _euiManager.SendUpdates(); + _voteManager.Update(); + break; + } case ModUpdateLevel.FramePostEngine: _updateManager.Update(); - _playTimeTracking?.Update(); + _playTimeTracking.Update(); _watchlistWebhookManager.Update(); - _connectionManager?.Update(); + _connection.Update(); break; } } protected override void Dispose(bool disposing) { - _playTimeTracking?.Shutdown(); - _dbManager?.Shutdown(); - IoCManager.Resolve().Shutdown(); - - IoCManager.Resolve().Shutdown(); - IoCManager.Resolve().Shutdown(); + var dest = _cfg.GetCVar(CCVars.DestinationFile); + if (!string.IsNullOrEmpty(dest)) + { + _playTimeTracking.Shutdown(); + _dbManager.Shutdown(); + } + _serverApi.Shutdown(); + _discordLink.Shutdown(); + _discordChatLink.Shutdown(); } private static void LoadConfigPresets(IConfigurationManager cfg, IResourceManager res, ISawmill sawmill) diff --git a/Content.Server/GameTicking/Commands/SetGamePresetCommand.cs b/Content.Server/GameTicking/Commands/SetGamePresetCommand.cs index 78e2b452b7b..23d0dc1e31f 100644 --- a/Content.Server/GameTicking/Commands/SetGamePresetCommand.cs +++ b/Content.Server/GameTicking/Commands/SetGamePresetCommand.cs @@ -2,7 +2,6 @@ using Content.Server.Administration; using Content.Server.GameTicking.Presets; using Content.Shared.Administration; -using Linguini.Shared.Util; using Robust.Shared.Console; using Robust.Shared.Prototypes; @@ -20,7 +19,7 @@ public sealed class SetGamePresetCommand : IConsoleCommand public void Execute(IConsoleShell shell, string argStr, string[] args) { - if (!args.Length.InRange(1, 2)) + if (args.Length is <= 1 or >= 3) { shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 1), ("upper", 2), ("currentAmount", args.Length))); return; diff --git a/Content.Server/IP/IPAddressExt.cs b/Content.Server/IP/IPAddressExt.cs index a61477e01bc..8c514e96b91 100644 --- a/Content.Server/IP/IPAddressExt.cs +++ b/Content.Server/IP/IPAddressExt.cs @@ -11,22 +11,14 @@ public static class IPAddressExt { // Npgsql used to map inet types as a tuple like this. // I'm upgrading the dependencies and I don't wanna rewrite a bunch of DB code, so a few helpers it shall be. - [return: NotNullIfNotNull(nameof(tuple))] - public static NpgsqlInet? ToNpgsqlInet(this (IPAddress, int)? tuple) + public static NpgsqlInet ToNpgsqlInet(this (IPAddress, int) tuple) { - if (tuple == null) - return null; - - return new NpgsqlInet(tuple.Value.Item1, (byte) tuple.Value.Item2); + return new NpgsqlInet(tuple.Item1, (byte)tuple.Item2); } - [return: NotNullIfNotNull(nameof(inet))] - public static (IPAddress, int)? ToTuple(this NpgsqlInet? inet) + public static (IPAddress, int) ToTuple(this NpgsqlInet inet) { - if (inet == null) - return null; - - return (inet.Value.Address, inet.Value.Netmask); + return (inet.Address, inet.Netmask); } // Taken from https://stackoverflow.com/a/56461160/4678631 diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index fb9fcf40baf..e7c90b0a198 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -32,65 +32,65 @@ using Content.Shared.Administration.Logs; using Content.Shared.Administration.Managers; using Content.Shared.Chat; +using Content.Shared.IoC; using Content.Shared.Kitchen; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.RateLimiting; -namespace Content.Server.IoC +namespace Content.Server.IoC; + +internal static class ServerContentIoC { - internal static class ServerContentIoC + public static void Register(IDependencyCollection deps) { - public static void Register() - { - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); // Lua - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); // Corvax-Queue - IoCManager.Register(); // Corvax-TTS - IoCManager.Register(); // Corvax-DiscordAuth - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); //Frontier - - IoCManager.Register(); - IoCManager.Register(); - } + SharedContentIoC.Register(deps); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); // Lua + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); // Corvax-Queue + deps.Register(); // Corvax-TTS + deps.Register(); // Corvax-DiscordAuth + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); + deps.Register(); //Frontier + deps.Register(); + deps.Register(); } } diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs index a729a9cc2b5..d03789d871f 100644 --- a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -1,6 +1,3 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Content.Server.Database; using Content.Shared.CCVar; using Content.Shared.Ghost.Roles; // Frontier: Ghost Role handling @@ -10,10 +7,13 @@ using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Configuration; +using Robust.Shared.Localization; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Serilog; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace Content.Server.Players.JobWhitelist; @@ -25,9 +25,11 @@ public sealed class JobWhitelistManager : IPostInjectInit [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly UserDbDataManager _userDb = default!; + [Dependency] private readonly ILogManager _logManager = default!; private readonly Dictionary> _whitelists = new(); private readonly Dictionary _globalWhitelists = new(); // Frontier + private ISawmill _sawmill = default!; public void Initialize() { @@ -93,7 +95,7 @@ public bool IsWhitelisted(NetUserId player, ProtoId job) if (!_whitelists.TryGetValue(player, out var whitelists) || // Frontier: added globalWhitelist check !_globalWhitelists.TryGetValue(player, out var globalWhitelist)) // Frontier { - Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", + _sawmill.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", player, job, Environment.StackTrace); @@ -153,7 +155,7 @@ public bool IsWhitelisted(NetUserId player, ProtoId ghostRol if (!_whitelists.TryGetValue(player, out var whitelists) || !_globalWhitelists.TryGetValue(player, out var globalWhitelist)) { - Log.Error("Unable to check if player {Player} is whitelisted for {GhostRole}. Stack trace:\\n{StackTrace}", + _sawmill.Error("Unable to check if player {Player} is whitelisted for {GhostRole}. Stack trace:\\n{StackTrace}", player, ghostRole, Environment.StackTrace); @@ -190,7 +192,7 @@ public bool IsGloballyWhitelisted(NetUserId player) if (!_globalWhitelists.TryGetValue(player, out var whitelist)) { - Log.Error("Unable to check if player {Player} is globally whitelisted. Stack trace:\\n{StackTrace}", + _sawmill.Error("Unable to check if player {Player} is globally whitelisted. Stack trace:\\n{StackTrace}", player, Environment.StackTrace); return false; @@ -226,5 +228,6 @@ void IPostInjectInit.PostInject() _userDb.AddOnLoadPlayer(LoadData); _userDb.AddOnFinishLoad(FinishLoad); _userDb.AddOnPlayerDisconnect(ClientDisconnected); + _sawmill = _logManager.GetSawmill("job_whitelist"); } } diff --git a/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs index 2f850faab13..76de8a25682 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnOnDespawnSystem.cs @@ -18,7 +18,8 @@ private void OnDespawn(EntityUid uid, SpawnOnDespawnComponent comp, ref TimedDes if (!TryComp(uid, out TransformComponent? xform)) return; - Spawn(comp.Prototype, xform.Coordinates); + var mapCoords = EntityManager.System().GetMapCoordinates(xform); + Spawn(comp.Prototype, mapCoords); } public void SetPrototype(Entity entity, EntProtoId prototype) diff --git a/Content.Server/Tabletop/TabletopCheckerSetup.cs b/Content.Server/Tabletop/TabletopCheckerSetup.cs index aa38bac7a36..865017ce3cd 100644 --- a/Content.Server/Tabletop/TabletopCheckerSetup.cs +++ b/Content.Server/Tabletop/TabletopCheckerSetup.cs @@ -23,10 +23,7 @@ public sealed partial class TabletopCheckerSetup : TabletopSetup public override void SetupTabletop(TabletopSession session, IEntityManager entityManager) { - session.Entities.Add( - entityManager.SpawnEntity(BoardPrototype, session.Position.Offset(-1, 0)) - ); - + SpawnPiece(entityManager, session, BoardPrototype, session.Position.Offset(-1, 0)); SpawnPieces(session, entityManager, session.Position.Offset(-4.5f, 3.5f)); } @@ -34,9 +31,6 @@ private void SpawnPieces(TabletopSession session, IEntityManager entityManager, { static float GetOffset(float offset) => offset * 1f /* separation */; - Span pieces = stackalloc EntityUid[42]; - var pieceIndex = 0; - // Pieces for (var offsetY = 0; offsetY < 3; offsetY++) { @@ -47,15 +41,14 @@ private void SpawnPieces(TabletopSession session, IEntityManager entityManager, // Prevents an extra piece on the middle row if (checker + offsetX > 8) continue; - pieces[pieceIndex] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypePieceBlack, left.Offset(GetOffset(offsetX + (1 - checker)), GetOffset(offsetY * -1)) ); - pieces[pieceIndex] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypePieceWhite, left.Offset(GetOffset(offsetX + checker), GetOffset(offsetY - 7)) ); - pieceIndex += 2; } } @@ -69,36 +62,37 @@ private void SpawnPieces(TabletopSession session, IEntityManager entityManager, for (var i = 0; i < NumCrowns; i++) { var step = -(Overlap * i); - pieces[pieceIndex] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypeCrownBlack, left.Offset(GetOffset(xOffsetBlack), GetOffset(step)) ); - pieces[pieceIndex + 1] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypeCrownWhite, left.Offset(GetOffset(xOffsetWhite), GetOffset(step)) ); - pieceIndex += 2; } // Spares for (var i = 0; i < 6; i++) { var step = -((Overlap * (NumCrowns + 2)) + (Overlap * i)); - pieces[pieceIndex] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypePieceBlack, left.Offset(GetOffset(xOffsetBlack), GetOffset(step)) ); - pieces[pieceIndex] = entityManager.SpawnEntity( + SpawnPiece(entityManager, session, PrototypePieceWhite, left.Offset(GetOffset(xOffsetWhite), GetOffset(step)) ); - pieceIndex += 2; } + } - for (var i = 0; i < pieces.Length; i++) - { - session.Entities.Add(pieces[i]); - } + private EntityUid SpawnPiece(IEntityManager entityManager, TabletopSession session, string proto, MapCoordinates coords) + { + var uid = entityManager.SpawnEntity(proto, coords); + entityManager.GetComponent(uid).LocalRotation = Angle.Zero; + session.Entities.Add(uid); + return uid; } } } diff --git a/Content.Server/Tabletop/TabletopChessSetup.cs b/Content.Server/Tabletop/TabletopChessSetup.cs index b636b8c05fb..9310ec02e1d 100644 --- a/Content.Server/Tabletop/TabletopChessSetup.cs +++ b/Content.Server/Tabletop/TabletopChessSetup.cs @@ -13,10 +13,7 @@ public sealed partial class TabletopChessSetup : TabletopSetup public override void SetupTabletop(TabletopSession session, IEntityManager entityManager) { - var chessboard = entityManager.SpawnEntity(BoardPrototype, session.Position.Offset(-1, 0)); - - session.Entities.Add(chessboard); - + SpawnPiece(entityManager, session, BoardPrototype, session.Position.Offset(-1, 0)); SpawnPieces(session, entityManager, session.Position.Offset(-4.5f, 3.5f)); } @@ -33,10 +30,8 @@ private void SpawnPieces(TabletopSession session, IEntityManager entityManager, SpawnPiecesRow(session, entityManager, "White", new MapCoordinates(x, y - 7 * separation, mapId), separation); // Extra queens - EntityUid tempQualifier = entityManager.SpawnEntity("BlackQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 3 * separation, mapId)); - session.Entities.Add(tempQualifier); - EntityUid tempQualifier1 = entityManager.SpawnEntity("WhiteQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 4 * separation, mapId)); - session.Entities.Add(tempQualifier1); + SpawnPiece(entityManager, session, "BlackQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 3 * separation, mapId)); + SpawnPiece(entityManager, session, "WhiteQueen", new MapCoordinates(x + 9 * separation + 9f / 32, y - 4 * separation, mapId)); } // TODO: refactor to load FEN instead @@ -51,24 +46,19 @@ private void SpawnPiecesRow(TabletopSession session, IEntityManager entityManage switch (piecesRow[i]) { case 'r': - EntityUid tempQualifier = entityManager.SpawnEntity(color + "Rook", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier); + SpawnPiece(entityManager, session, color + "Rook", new MapCoordinates(x + i * separation, y, mapId)); break; case 'n': - EntityUid tempQualifier1 = entityManager.SpawnEntity(color + "Knight", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier1); + SpawnPiece(entityManager, session, color + "Knight", new MapCoordinates(x + i * separation, y, mapId)); break; case 'b': - EntityUid tempQualifier2 = entityManager.SpawnEntity(color + "Bishop", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier2); + SpawnPiece(entityManager, session, color + "Bishop", new MapCoordinates(x + i * separation, y, mapId)); break; case 'q': - EntityUid tempQualifier3 = entityManager.SpawnEntity(color + "Queen", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier3); + SpawnPiece(entityManager, session, color + "Queen", new MapCoordinates(x + i * separation, y, mapId)); break; case 'k': - EntityUid tempQualifier4 = entityManager.SpawnEntity(color + "King", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier4); + SpawnPiece(entityManager, session, color + "King", new MapCoordinates(x + i * separation, y, mapId)); break; } } @@ -81,9 +71,16 @@ private void SpawnPawns(TabletopSession session, IEntityManager entityManager, s for (int i = 0; i < 8; i++) { - EntityUid tempQualifier = entityManager.SpawnEntity(color + "Pawn", new MapCoordinates(x + i * separation, y, mapId)); - session.Entities.Add(tempQualifier); + SpawnPiece(entityManager, session, color + "Pawn", new MapCoordinates(x + i * separation, y, mapId)); } } + + private EntityUid SpawnPiece(IEntityManager entityManager, TabletopSession session, string proto, MapCoordinates coords) + { + var uid = entityManager.SpawnEntity(proto, coords); + entityManager.GetComponent(uid).LocalRotation = Angle.Zero; + session.Entities.Add(uid); + return uid; + } } } diff --git a/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs b/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs index 468bfdc0f8c..c725b2bc34b 100644 --- a/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs +++ b/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs @@ -371,14 +371,7 @@ private async void CreateVotekickVote(ICommonSession? initiator, string[]? args) } var targetUid = located.UserId; var targetHWid = located.LastHWId; - (IPAddress, int)? targetIP = null; - - if (located.LastAddress is not null) - { - targetIP = located.LastAddress.AddressFamily is AddressFamily.InterNetwork - ? (located.LastAddress, 32) // People with ipv4 addresses get a /32 address so we ban that - : (located.LastAddress, 64); // This can only be an ipv6 address. People with ipv6 address should get /64 addresses so we ban that. - } + var targetIP = located.LastAddress; if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession)) { @@ -544,7 +537,15 @@ private async void CreateVotekickVote(ICommonSession? initiator, string[]? args) uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration); - _bans.CreateServerBan(targetUid, target, null, targetIP, targetHWid, minutes, severity, Loc.GetString("votekick-ban-reason", ("reason", reason))); + var banInfo = new CreateServerBanInfo(Loc.GetString("votekick-ban-reason", ("reason", reason))); + banInfo.AddUser(targetUid, target); + banInfo.AddHWId(targetHWid); + banInfo.AddAddress(targetIP); + banInfo.WithSeverity(severity); + if (minutes > 0) + banInfo.WithMinutes(minutes); + + _bans.CreateServerBan(banInfo); } } else diff --git a/Content.Server/Worldgen/Prototypes/GCQueuePrototype.cs b/Content.Server/Worldgen/Prototypes/GCQueuePrototype.cs index 9d55ad605fc..d41b2a196fb 100644 --- a/Content.Server/Worldgen/Prototypes/GCQueuePrototype.cs +++ b/Content.Server/Worldgen/Prototypes/GCQueuePrototype.cs @@ -6,36 +6,35 @@ namespace Content.Server.Worldgen.Prototypes; /// This is a prototype for a GC queue. /// [Prototype("gcQueue")] -public sealed class GCQueuePrototype : IPrototype +public sealed partial class GCQueuePrototype : IPrototype { /// [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; /// /// How deep the GC queue is at most. If this value is ever exceeded entities get processed automatically regardless of /// tick-time cap. /// [DataField("depth", required: true)] - public int Depth { get; } + public int Depth { get; private set; } /// /// The maximum amount of time that can be spent processing this queue. /// [DataField("maximumTickTime")] - public TimeSpan MaximumTickTime { get; } = TimeSpan.FromMilliseconds(3); // 1->3 Mono + public TimeSpan MaximumTickTime { get; private set; } = TimeSpan.FromMilliseconds(3); // 1->3 Mono /// /// The minimum depth before entities in the queue actually get processed for deletion. /// [DataField("minDepthToProcess", required: true)] - public int MinDepthToProcess { get; } + public int MinDepthToProcess { get; private set; } /// /// Whether or not the GC should fire an event on the entity to see if it's eligible to skip the queue. /// Useful for making it so only objects a player has actually interacted with get put in the collection queue. /// [DataField("trySkipQueue")] - public bool TrySkipQueue { get; } + public bool TrySkipQueue { get; private set; } } - diff --git a/Content.Server/_Lua/AiShuttle/AiShuttleSpawnRuleComponent.cs b/Content.Server/_Lua/AiShuttle/AiShuttleSpawnRuleComponent.cs index c830ade529d..b53eaa12b5d 100644 --- a/Content.Server/_Lua/AiShuttle/AiShuttleSpawnRuleComponent.cs +++ b/Content.Server/_Lua/AiShuttle/AiShuttleSpawnRuleComponent.cs @@ -55,7 +55,7 @@ public enum AiShuttleDatasetNameType } [DataRecord] -public sealed class AiShuttleDungeonSpawnGroup : IAiShuttleSpawnGroup +public sealed partial class AiShuttleDungeonSpawnGroup : IAiShuttleSpawnGroup { public List> Protos = new(); public float MinimumDistance { get; } @@ -74,7 +74,7 @@ public sealed class AiShuttleDungeonSpawnGroup : IAiShuttleSpawnGroup } [DataRecord] -public sealed class AiShuttleGridSpawnGroup : IAiShuttleSpawnGroup +public sealed partial class AiShuttleGridSpawnGroup : IAiShuttleSpawnGroup { public List Paths = new(); public float MinimumDistance { get; } diff --git a/Content.Server/_NF/StationEvents/Components/BluespaceErrorRuleComponent.cs b/Content.Server/_NF/StationEvents/Components/BluespaceErrorRuleComponent.cs index 5aebcc00311..75fc0e60a4b 100644 --- a/Content.Server/_NF/StationEvents/Components/BluespaceErrorRuleComponent.cs +++ b/Content.Server/_NF/StationEvents/Components/BluespaceErrorRuleComponent.cs @@ -121,7 +121,7 @@ public enum BluespaceDatasetNameType } [DataRecord] -public sealed class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup +public sealed partial class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup { /// /// Prototypes we can choose from to spawn. @@ -164,7 +164,7 @@ public sealed class BluespaceDungeonSpawnGroup : IBluespaceSpawnGroup } [DataRecord] -public sealed class BluespaceGridSpawnGroup : IBluespaceSpawnGroup +public sealed partial class BluespaceGridSpawnGroup : IBluespaceSpawnGroup { public List Paths = new(); diff --git a/Content.Shared.Database/Bans.cs b/Content.Shared.Database/Bans.cs new file mode 100644 index 00000000000..9fb100f5db4 --- /dev/null +++ b/Content.Shared.Database/Bans.cs @@ -0,0 +1,33 @@ +namespace Content.Shared.Database; + +/// +/// Types of bans that can be stored in the database. +/// +public enum BanType : byte +{ + /// + /// A ban from the entire server. If a player matches the ban info, they will be refused connection. + /// + Server, + + /// + /// A ban from playing one or more roles. + /// + Role, +} + +/// +/// A single role for a database role ban. +/// +/// The type of role being banned, e.g. Job. +/// +/// The ID of the role being banned. This is likely a prototype ID based on . +/// +[Serializable] +public record struct BanRoleDef(string RoleType, string RoleId) +{ + public override string ToString() + { + return $"{RoleType}:{RoleId}"; + } +} diff --git a/Content.Shared.Database/Content.Shared.Database.csproj b/Content.Shared.Database/Content.Shared.Database.csproj index bc2cc300bff..5b14175f4fe 100644 --- a/Content.Shared.Database/Content.Shared.Database.csproj +++ b/Content.Shared.Database/Content.Shared.Database.csproj @@ -1,8 +1,9 @@ enable - enable + + diff --git a/Content.Shared/ADT/AnimatedLobbyScreen/AnimatedLobbyScreenPrototype.cs b/Content.Shared/ADT/AnimatedLobbyScreen/AnimatedLobbyScreenPrototype.cs index 7ec55671be7..615f49f368c 100644 --- a/Content.Shared/ADT/AnimatedLobbyScreen/AnimatedLobbyScreenPrototype.cs +++ b/Content.Shared/ADT/AnimatedLobbyScreen/AnimatedLobbyScreenPrototype.cs @@ -10,8 +10,8 @@ public sealed partial class AnimatedLobbyScreenPrototype : IPrototype { /// [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; [DataField(required: true)] public string Path = default!; -} \ No newline at end of file +} diff --git a/Content.Shared/Actions/Components/ActionComponent.cs b/Content.Shared/Actions/Components/ActionComponent.cs index 2833aa1798d..8f9b7fa925d 100644 --- a/Content.Shared/Actions/Components/ActionComponent.cs +++ b/Content.Shared/Actions/Components/ActionComponent.cs @@ -197,7 +197,7 @@ public EntityUid? EntityIcon } [DataRecord, Serializable, NetSerializable] -public record struct ActionCooldown +public partial record struct ActionCooldown { [DataField(required: true, customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan Start; diff --git a/Content.Shared/Administration/BanList/BanListEuiState.cs b/Content.Shared/Administration/BanList/BanListEuiState.cs index 09faa9706ea..c885ff1f706 100644 --- a/Content.Shared/Administration/BanList/BanListEuiState.cs +++ b/Content.Shared/Administration/BanList/BanListEuiState.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Administration.BanList; [Serializable, NetSerializable] public sealed class BanListEuiState : EuiStateBase { - public BanListEuiState(string banListPlayerName, List bans, List roleBans) + public BanListEuiState(string banListPlayerName, List bans, List roleBans) { BanListPlayerName = banListPlayerName; Bans = bans; @@ -14,6 +14,6 @@ public BanListEuiState(string banListPlayerName, List bans, Lis } public string BanListPlayerName { get; } - public List Bans { get; } - public List RoleBans { get; } + public List Bans { get; } + public List RoleBans { get; } } diff --git a/Content.Shared/Administration/BanList/SharedBan.cs b/Content.Shared/Administration/BanList/SharedBan.cs new file mode 100644 index 00000000000..68ccc02c58f --- /dev/null +++ b/Content.Shared/Administration/BanList/SharedBan.cs @@ -0,0 +1,20 @@ +using System.Collections.Immutable; +using Content.Shared.Database; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Administration.BanList; + +[Serializable, NetSerializable] +public record SharedBan( + int? Id, + BanType Type, + ImmutableArray UserIds, + ImmutableArray<(string address, int cidrMask)> Addresses, + ImmutableArray HWIds, + DateTime BanTime, + DateTime? ExpirationTime, + string Reason, + string? BanningAdminName, + SharedUnban? Unban, + ImmutableArray? Roles); diff --git a/Content.Shared/Administration/BanList/SharedServerBan.cs b/Content.Shared/Administration/BanList/SharedServerBan.cs deleted file mode 100644 index a8b9ce0d9a1..00000000000 --- a/Content.Shared/Administration/BanList/SharedServerBan.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Robust.Shared.Network; -using Robust.Shared.Serialization; - -namespace Content.Shared.Administration.BanList; - -[Serializable, NetSerializable] -public record SharedServerBan( - int? Id, - NetUserId? UserId, - (string address, int cidrMask)? Address, - string? HWId, - DateTime BanTime, - DateTime? ExpirationTime, - string Reason, - string? BanningAdminName, - SharedServerUnban? Unban -); diff --git a/Content.Shared/Administration/BanList/SharedServerRoleBan.cs b/Content.Shared/Administration/BanList/SharedServerRoleBan.cs deleted file mode 100644 index fca2ea15839..00000000000 --- a/Content.Shared/Administration/BanList/SharedServerRoleBan.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Network; -using Robust.Shared.Serialization; - -namespace Content.Shared.Administration.BanList; - -[Serializable, NetSerializable] -public sealed record SharedServerRoleBan( - int? Id, - NetUserId? UserId, - (string address, int cidrMask)? Address, - string? HWId, - DateTime BanTime, - DateTime? ExpirationTime, - string Reason, - string? BanningAdminName, - SharedServerUnban? Unban, - string Role -) : SharedServerBan(Id, UserId, Address, HWId, BanTime, ExpirationTime, Reason, BanningAdminName, Unban); diff --git a/Content.Shared/Administration/BanList/SharedServerUnban.cs b/Content.Shared/Administration/BanList/SharedUnban.cs similarity index 81% rename from Content.Shared/Administration/BanList/SharedServerUnban.cs rename to Content.Shared/Administration/BanList/SharedUnban.cs index f3a57e41590..d60bb9184e2 100644 --- a/Content.Shared/Administration/BanList/SharedServerUnban.cs +++ b/Content.Shared/Administration/BanList/SharedUnban.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Administration.BanList; [Serializable, NetSerializable] -public sealed record SharedServerUnban( +public sealed record SharedUnban( string? UnbanningAdmin, DateTime UnbanTime ); diff --git a/Content.Shared/Administration/Notes/SharedAdminNote.cs b/Content.Shared/Administration/Notes/SharedAdminNote.cs index 09d4f3f9478..d849d350784 100644 --- a/Content.Shared/Administration/Notes/SharedAdminNote.cs +++ b/Content.Shared/Administration/Notes/SharedAdminNote.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using Content.Shared.Database; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -7,8 +8,8 @@ namespace Content.Shared.Administration.Notes; [Serializable, NetSerializable] public sealed record SharedAdminNote( int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note. - NetUserId Player, // Notes player - int? Round, // Which round was it added in? + ImmutableArray Players, // Notes player + ImmutableArray Rounds, // Which round was it added in? string? ServerName, // Which server was this added on? TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note NoteType NoteType, // Type of note @@ -20,7 +21,7 @@ public sealed record SharedAdminNote( DateTime CreatedAt, // When was it created? DateTime? LastEditedAt, // When was it last edited? DateTime? ExpiryTime, // Does it expire? - string[]? BannedRoles, // Only valid for role bans. List of banned roles + ImmutableArray? BannedRoles, // Only valid for role bans. List of banned roles DateTime? UnbannedTime, // Only valid for bans. Set if unbanned string? UnbannedByName, // Only valid for bans. Set if unbanned bool? Seen // Only valid for messages, otherwise should be null. Has the user seen this message? diff --git a/Content.Shared/Audio/AmbientMusicPrototype.cs b/Content.Shared/Audio/AmbientMusicPrototype.cs index db39d0fb164..16b92d21e88 100644 --- a/Content.Shared/Audio/AmbientMusicPrototype.cs +++ b/Content.Shared/Audio/AmbientMusicPrototype.cs @@ -11,7 +11,7 @@ namespace Content.Shared.Audio; [Prototype] public sealed partial class AmbientMusicPrototype : IPrototype { - [IdDataField] public string ID { get; } = string.Empty; + [IdDataField] public string ID { get; private set; } = string.Empty; [ViewVariables(VVAccess.ReadWrite), DataField("sound", required: true)] public SoundSpecifier Sound = default!; diff --git a/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs index 0138009ef9c..b75ed796e2e 100644 --- a/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs @@ -21,7 +21,7 @@ public sealed partial class EmoteSoundsPrototype : IPrototype, IInheritingProtot /// [AbstractDataField] [NeverPushInheritance] - public bool Abstract { get; } + public bool Abstract { get; private set; } /// /// Optional fallback sound that will play if collection diff --git a/Content.Shared/Chemistry/Prototypes/InjectorModePrototype.cs b/Content.Shared/Chemistry/Prototypes/InjectorModePrototype.cs new file mode 100644 index 00000000000..0255d8798f5 --- /dev/null +++ b/Content.Shared/Chemistry/Prototypes/InjectorModePrototype.cs @@ -0,0 +1,145 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; + +namespace Content.Shared.Chemistry.Prototypes; + +/// +/// This defines the behavior of an injector. +/// Every injector requires this and it defines how much an injector injects, what transferamounts they can switch between, etc. +/// +[Prototype] +public sealed partial class InjectorModePrototype : IPrototype, IInheritingPrototype +{ + /// + [IdDataField] + public string ID { get; private set; } = default!; + + /// + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; private set; } + + /// + [AbstractDataField, NeverPushInheritance] + public bool Abstract { get; private set; } + + /// + /// The name of the mode that will be shown on the label UI. + /// + [DataField(required: true)] + public LocId Name; + + /// + /// If true, it'll inject the user when used in hand (Default Key: Y/Z) + /// + [DataField] + public bool InjectOnUse; + + /// + /// The transfer amounts for the set-transfer verb. + /// + [DataField] + public List TransferAmounts = new() { 1, 5, 10, 15 }; + + /// + /// Injection/Drawing delay (seconds) when the target is a mob. + /// + [DataField] + public TimeSpan MobTime = TimeSpan.FromSeconds(5); + + /// + /// The delay to draw Reagents from Containers. + /// If set, RefillTime should probably have the same value. + /// + [DataField] + public TimeSpan ContainerDrawTime = TimeSpan.Zero; + + + /// + /// The number to multiply and if the target is the downed. + /// Downed counts as crouching, buckled on a bed or critical. + /// + [DataField] + public float DownedModifier = 0.5f; + + /// + /// The number to multiply and if the target is the user. + /// + [DataField] + public float SelfModifier = 0.5f; + + /// + /// This delay will increase the DoAfter time for each Xu above . + /// + [DataField] + public TimeSpan DelayPerVolume = TimeSpan.FromSeconds(0.1); + + /// + /// This works in tandem with . + /// + [DataField] + public FixedPoint2 IgnoreDelayForVolume = FixedPoint2.New(5); + + /// + /// What message will be displayed to the user when attempting to inject someone. + /// + /// + /// This is used for when you aren't injecting with a needle or an instant hypospray. + /// It would be weird if someone injects with a spray, but the popup says "needle". + /// + [DataField] + public LocId PopupUserAttempt = "injector-component-needle-injecting-user"; + + /// + /// What message will be displayed to the target when someone attempts to inject into them. + /// + [DataField] + public LocId PopupTargetAttempt = "injector-component-needle-injecting-target"; + + /// + /// The state of the injector. Determines its attack behavior. Containers must have the + /// right SolutionCaps to support injection/drawing. For InjectOnly injectors this should + /// only ever be set to Inject + /// + [DataField] + public InjectorBehavior Behavior = InjectorBehavior.Inject; + + /// + /// Sound that will be played when injecting. + /// + [DataField] + public SoundSpecifier? InjectSound; + + /// + /// A popup for the target upon a successful injection. + /// It's imperative that this is not null when is instant. + /// + [DataField] + public LocId? InjectPopupTarget; + +} + +/// +/// Possible modes for an . +/// +[Serializable, NetSerializable, Flags] +public enum InjectorBehavior +{ + /// + /// The injector will try to inject reagent into things. + /// + Inject = 1 << 0, + + /// + /// The injector will try to draw reagent from things. + /// + Draw = 1 << 1, + + /// + /// The injector will draw from containers and inject into mobs. + /// + Dynamic = 1 << 2, +} diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index 52ed717e960..da32cdf817d 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -1,28 +1,21 @@  - - $(TargetFramework) - 12 - false false RA0032;nullable - enable + + + + + + - - false - - - false - - - false - + diff --git a/Content.Shared/Corvax/TTS/TTSVoicePrototype.cs b/Content.Shared/Corvax/TTS/TTSVoicePrototype.cs index 124683c4e6c..768cc0ff64b 100644 --- a/Content.Shared/Corvax/TTS/TTSVoicePrototype.cs +++ b/Content.Shared/Corvax/TTS/TTSVoicePrototype.cs @@ -11,24 +11,24 @@ namespace Content.Shared.Corvax.TTS; public sealed partial class TTSVoicePrototype : IPrototype { [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; [DataField("name")] - public string Name { get; } = string.Empty; + public string Name { get; private set; } = string.Empty; [DataField("sex", required: true)] - public Sex Sex { get; } = default!; + public Sex Sex { get; private set; } = default!; [ViewVariables(VVAccess.ReadWrite)] [DataField("speaker", required: true)] - public string Speaker { get; } = string.Empty; + public string Speaker { get; private set; } = string.Empty; /// /// Whether the species is available "at round start" (In the character editor) /// [DataField("roundStart")] - public bool RoundStart { get; } = true; + public bool RoundStart { get; private set; } = true; [DataField("sponsorOnly")] - public bool SponsorOnly { get; } = false; + public bool SponsorOnly { get; private set; } = false; } diff --git a/Content.Shared/CrewManifest/SharedCrewManifestSystem.cs b/Content.Shared/CrewManifest/SharedCrewManifestSystem.cs index a9279cc7f1f..72c6a7bde84 100644 --- a/Content.Shared/CrewManifest/SharedCrewManifestSystem.cs +++ b/Content.Shared/CrewManifest/SharedCrewManifestSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.Eui; -using NetSerializer; using Robust.Shared.Serialization; namespace Content.Shared.CrewManifest; diff --git a/Content.Shared/Entry/EntryPoint.cs b/Content.Shared/Entry/EntryPoint.cs index df267b08cb1..db8d6a6abdd 100644 --- a/Content.Shared/Entry/EntryPoint.cs +++ b/Content.Shared/Entry/EntryPoint.cs @@ -27,7 +27,6 @@ public sealed class EntryPoint : GameShared public override void PreInit() { IoCManager.InjectDependencies(this); - SharedContentIoC.Register(); } public override void Shutdown() diff --git a/Content.Shared/Fluids/SharedPuddleSystem.cs b/Content.Shared/Fluids/SharedPuddleSystem.cs index d1f2ff8344c..416ebb7e800 100644 --- a/Content.Shared/Fluids/SharedPuddleSystem.cs +++ b/Content.Shared/Fluids/SharedPuddleSystem.cs @@ -22,11 +22,11 @@ public abstract partial class SharedPuddleSystem : EntitySystem [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - private static readonly ProtoId Blood = "Blood"; - private static readonly ProtoId Slime = "Slime"; - private static readonly ProtoId CopperBlood = "CopperBlood"; + private const string Blood = "Blood"; + private const string Slime = "Slime"; + private const string CopperBlood = "CopperBlood"; - private static readonly string[] StandoutReagents = [Blood, Slime, CopperBlood]; + private static readonly string[] StandoutReagents = { Blood, Slime, CopperBlood }; /// /// The lowest threshold to be considered for puddle sprite states as well as slipperiness of a puddle. diff --git a/Content.Shared/IoC/SharedContentIoC.cs b/Content.Shared/IoC/SharedContentIoC.cs index c20cdbc111e..f23b9f83558 100644 --- a/Content.Shared/IoC/SharedContentIoC.cs +++ b/Content.Shared/IoC/SharedContentIoC.cs @@ -5,10 +5,10 @@ namespace Content.Shared.IoC { public static class SharedContentIoC { - public static void Register() + public static void Register(IDependencyCollection deps) { - IoCManager.Register(); - IoCManager.Register(); + deps.Register(); + deps.Register(); } } } diff --git a/Content.Shared/Lathe/LatheMessages.cs b/Content.Shared/Lathe/LatheMessages.cs index 027d19d7666..5b9ee566afa 100644 --- a/Content.Shared/Lathe/LatheMessages.cs +++ b/Content.Shared/Lathe/LatheMessages.cs @@ -1,5 +1,4 @@ using Content.Shared.Research.Prototypes; -using NetSerializer; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; diff --git a/Content.Shared/Nutrition/Prototypes/EdiblePrototype.cs b/Content.Shared/Nutrition/Prototypes/EdiblePrototype.cs new file mode 100644 index 00000000000..864383610c9 --- /dev/null +++ b/Content.Shared/Nutrition/Prototypes/EdiblePrototype.cs @@ -0,0 +1,60 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Nutrition.Prototypes; + +/// +/// This stores unique data for an item that is edible, such as verbs, verb icons, verb names, sounds, ect. +/// +[Prototype] +public sealed partial class EdiblePrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The sound we make when eaten. + /// + [DataField] + public SoundSpecifier UseSound = new SoundCollectionSpecifier("eating"); + + /// + /// The localization identifier for the user's ingestion message. + /// + [DataField] + public LocId Message; + + /// + /// The localization identifier for an observer's or "others'" ingestion message. + /// + [DataField] + public LocId OtherMessage; + + /// + /// Localization verb used when consuming this item. + /// + [DataField] + public LocId Verb; + + /// + /// Localization noun used when consuming this item. + /// + [DataField] + public LocId Noun; + + /// + /// What type of food are we, currently used for determining verbs and some checks. + /// + [DataField] + public LocId VerbName; + + /// + /// What type of food are we, currently used for determining verbs and some checks. + /// + [DataField] + public SpriteSpecifier? VerbIcon; + + +} diff --git a/Content.Shared/Nyanotrasen/Kitchen/Prototypes/CrispinessLevelSetPrototype.cs b/Content.Shared/Nyanotrasen/Kitchen/Prototypes/CrispinessLevelSetPrototype.cs index dc532a7d45a..cdfc6968b2a 100644 --- a/Content.Shared/Nyanotrasen/Kitchen/Prototypes/CrispinessLevelSetPrototype.cs +++ b/Content.Shared/Nyanotrasen/Kitchen/Prototypes/CrispinessLevelSetPrototype.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Nyanotrasen.Kitchen.Prototypes; [Prototype("crispinessLevelSet")] public sealed partial class CrispinessLevelSetPrototype : IPrototype { - [IdDataField] public string ID { get; } = default!; + [IdDataField] public string ID { get; private set; } = default!; /// /// Crispiness level strings. The index is the crispiness value used, starting with 0. diff --git a/Content.Shared/Players/MsgRoleBans.cs b/Content.Shared/Players/MsgRoleBans.cs index fd90f62b0b5..84556f7bec3 100644 --- a/Content.Shared/Players/MsgRoleBans.cs +++ b/Content.Shared/Players/MsgRoleBans.cs @@ -1,5 +1,7 @@ +using Content.Shared.Roles; using Lidgren.Network; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Players; @@ -11,24 +13,36 @@ public sealed class MsgRoleBans : NetMessage { public override MsgGroups MsgGroup => MsgGroups.EntityEvent; - public List Bans = new(); + public List> JobBans = new(); + public List> AntagBans = new(); public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { - var count = buffer.ReadVariableInt32(); - Bans.EnsureCapacity(count); + var jobCount = buffer.ReadVariableInt32(); + JobBans.EnsureCapacity(jobCount); + for (var i = 0; i < jobCount; i++) + { + JobBans.Add(new ProtoId(buffer.ReadString())); + } - for (var i = 0; i < count; i++) + var antagCount = buffer.ReadVariableInt32(); + AntagBans.EnsureCapacity(antagCount); + for (var i = 0; i < antagCount; i++) { - Bans.Add(buffer.ReadString()); + AntagBans.Add(new ProtoId(buffer.ReadString())); } } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) { - buffer.WriteVariableInt32(Bans.Count); + buffer.WriteVariableInt32(JobBans.Count); + foreach (var ban in JobBans) + { + buffer.Write(ban); + } - foreach (var ban in Bans) + buffer.WriteVariableInt32(AntagBans.Count); + foreach (var ban in AntagBans) { buffer.Write(ban); } diff --git a/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs b/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs index 4291ecfb989..f71247f5722 100644 --- a/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs +++ b/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs @@ -1,4 +1,5 @@ using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; namespace Content.Shared.Preferences.Loadouts; @@ -6,11 +7,20 @@ namespace Content.Shared.Preferences.Loadouts; /// Corresponds to a set of loadouts for a particular slot. /// [Prototype] -public sealed partial class LoadoutGroupPrototype : IPrototype +public sealed partial class LoadoutGroupPrototype : IPrototype, IInheritingPrototype { [IdDataField] public string ID { get; private set; } = string.Empty; + /// + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; private set; } + + /// + [NeverPushInheritance] + [AbstractDataField] + public bool Abstract { get; private set; } + /// /// User-friendly name for the group. /// diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index f1b436b5906..6a91a5bef21 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -111,7 +111,7 @@ public sealed partial class JobPrototype : IPrototype /// Nyano/DV: For e.g. prisoners, they'll never use their latejoin spawner. /// [DataField("alwaysUseSpawner")] - public bool AlwaysUseSpawner { get; } = false; + public bool AlwaysUseSpawner { get; private set; } = false; /// /// The "weight" or importance of this job. If this number is large, the job system will assign this job diff --git a/Content.Shared/SmartFridge/SmartFridgeComponent.cs b/Content.Shared/SmartFridge/SmartFridgeComponent.cs index a41faaf9520..d268697c1e9 100644 --- a/Content.Shared/SmartFridge/SmartFridgeComponent.cs +++ b/Content.Shared/SmartFridge/SmartFridgeComponent.cs @@ -90,7 +90,7 @@ public sealed partial class SmartFridgeComponent : Component } [Serializable, NetSerializable, DataRecord] -public record struct SmartFridgeEntry +public partial record struct SmartFridgeEntry { public string Name; diff --git a/Content.Shared/_Crescent/SpaceBiomes/SpaceBiomePrototype.cs b/Content.Shared/_Crescent/SpaceBiomes/SpaceBiomePrototype.cs index 7efb39431ff..e9aa3e7eb85 100644 --- a/Content.Shared/_Crescent/SpaceBiomes/SpaceBiomePrototype.cs +++ b/Content.Shared/_Crescent/SpaceBiomes/SpaceBiomePrototype.cs @@ -3,7 +3,7 @@ namespace Content.Shared._Crescent.SpaceBiomes; [Prototype("ambientSpaceBiome")] -public sealed class SpaceBiomePrototype : IPrototype +public sealed partial class SpaceBiomePrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Content.Shared/_DV/Mail/MailDeliveryPoolPrototype.cs b/Content.Shared/_DV/Mail/MailDeliveryPoolPrototype.cs index eb118a2ff61..a2a6e9e9e04 100644 --- a/Content.Shared/_DV/Mail/MailDeliveryPoolPrototype.cs +++ b/Content.Shared/_DV/Mail/MailDeliveryPoolPrototype.cs @@ -6,9 +6,9 @@ namespace Content.Shared._DV.Mail; /// Generic random weighting dataset to use. /// [Prototype("mailDeliveryPool")] -public sealed class MailDeliveryPoolPrototype : IPrototype +public sealed partial class MailDeliveryPoolPrototype : IPrototype { - [IdDataField] public string ID { get; } = default!; + [IdDataField] public string ID { get; private set; } = default!; /// /// Mail that can be sent to everyone. diff --git a/Content.Shared/_DV/Whitelist/WhitelistTierPrototype.cs b/Content.Shared/_DV/Whitelist/WhitelistTierPrototype.cs index 14659baa4e3..5a788176a6f 100644 --- a/Content.Shared/_DV/Whitelist/WhitelistTierPrototype.cs +++ b/Content.Shared/_DV/Whitelist/WhitelistTierPrototype.cs @@ -5,10 +5,10 @@ namespace Content.Shared._DV.Whitelist; [Prototype("whitelistTier")] -public sealed class WhitelistTierPrototype : IPrototype +public sealed partial class WhitelistTierPrototype : IPrototype { [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; [DataField] public string Name = string.Empty; diff --git a/Content.Shared/_Emberfall/Weapons/Ranged/TracerComponent.cs b/Content.Shared/_Emberfall/Weapons/Ranged/TracerComponent.cs index 68fdebff3b3..0faaff46662 100644 --- a/Content.Shared/_Emberfall/Weapons/Ranged/TracerComponent.cs +++ b/Content.Shared/_Emberfall/Weapons/Ranged/TracerComponent.cs @@ -33,7 +33,7 @@ public sealed partial class TracerComponent : Component } [Serializable, NetSerializable, DataRecord] -public struct TracerData(List positionHistory, TimeSpan endTime) +public partial struct TracerData(List positionHistory, TimeSpan endTime) { /// /// The history of positions this tracer has moved through diff --git a/Content.Shared/_Lua/Language/LanguagePrototype.cs b/Content.Shared/_Lua/Language/LanguagePrototype.cs index b37d75bc011..13e49853909 100644 --- a/Content.Shared/_Lua/Language/LanguagePrototype.cs +++ b/Content.Shared/_Lua/Language/LanguagePrototype.cs @@ -4,10 +4,10 @@ namespace Content.Shared._Lua.Language; [Prototype("language")] -public sealed class LanguagePrototype : IPrototype +public sealed partial class LanguagePrototype : IPrototype { [IdDataField] - public string ID { get; private set; } = default!; + public string ID { get; private set; } = default!; [DataField("obfuscation")] public ObfuscationMethod Obfuscation = ObfuscationMethod.Default; diff --git a/Content.Shared/_Lua/SponsorLoadout/SponsorLoadoutPrototype.cs b/Content.Shared/_Lua/SponsorLoadout/SponsorLoadoutPrototype.cs index a2e72ce4da9..965fb9692d5 100644 --- a/Content.Shared/_Lua/SponsorLoadout/SponsorLoadoutPrototype.cs +++ b/Content.Shared/_Lua/SponsorLoadout/SponsorLoadoutPrototype.cs @@ -9,26 +9,26 @@ namespace Content.Shared._Lua.SponsorLoadout; public sealed partial class SponsorLoadoutPrototype : IPrototype { [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; [DataField("entity", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string EntityId { get; } = default!; + public string EntityId { get; private set; } = default!; [DataField("sponsorOnly")] public bool SponsorOnly = false; [DataField("whitelistJobs", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List? WhitelistJobs { get; } + public List? WhitelistJobs { get; private set; } [DataField("blacklistJobs", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List? BlacklistJobs { get; } + public List? BlacklistJobs { get; private set; } [DataField("speciesRestriction")] - public List? SpeciesRestrictions { get; } + public List? SpeciesRestrictions { get; private set; } [DataField] - public string? Login { get; } + public string? Login { get; private set; } [DataField("sponsorRole")] - public string? SponsorRole { get; } + public string? SponsorRole { get; private set; } } diff --git a/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs b/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs index 41b9ed09477..ede37ad9615 100644 --- a/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs +++ b/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs @@ -17,7 +17,7 @@ public AppraisalUiState(List appraisedItems) } [Serializable, NetSerializable, DataRecord] -public sealed class AppraisedItem +public sealed partial class AppraisedItem { public readonly string Name; public readonly string AppraisedPrice; diff --git a/Content.Shared/_NF/Roles/SharedJobTrackingSystem.cs b/Content.Shared/_NF/Roles/SharedJobTrackingSystem.cs index 88099076223..8dc5210826b 100644 --- a/Content.Shared/_NF/Roles/SharedJobTrackingSystem.cs +++ b/Content.Shared/_NF/Roles/SharedJobTrackingSystem.cs @@ -8,7 +8,13 @@ namespace Content.Shared._NF.Roles.Systems; /// public abstract class SharedJobTrackingSystem : EntitySystem { - public static readonly ProtoId[] ReopenExceptions = ["Contractor", "Pilot", "Mercenary", "Borg"]; + public static readonly ProtoId[] ReopenExceptions = + { + "Contractor", + "Pilot", + "Mercenary", + "Borg" + }; public static bool JobShouldBeReopened(ProtoId job) { diff --git a/Content.Shared/_NF/SectorServices/Prototypes/SectorServicePrototypes.cs b/Content.Shared/_NF/SectorServices/Prototypes/SectorServicePrototypes.cs index 919186bdd32..b13b2b480c4 100644 --- a/Content.Shared/_NF/SectorServices/Prototypes/SectorServicePrototypes.cs +++ b/Content.Shared/_NF/SectorServices/Prototypes/SectorServicePrototypes.cs @@ -22,5 +22,5 @@ public sealed partial class SectorServicePrototype : IPrototype /// A dictionary mapping a service to its necessary components. /// [DataField] - public ComponentRegistry Components { get; } = new(); + public ComponentRegistry Components { get; private set; } = new(); } diff --git a/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs b/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs index 6fc5de95043..3a49baa23ec 100644 --- a/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs +++ b/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs @@ -7,10 +7,10 @@ namespace Content.Shared._NF.Shipyard.Prototypes; [Prototype] -public sealed class VesselPrototype : IPrototype, IInheritingPrototype +public sealed partial class VesselPrototype : IPrototype, IInheritingPrototype { [IdDataField] - public string ID { get; } = default!; + public string ID { get; private set; } = default!; [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] public string[]? Parents { get; private set; } diff --git a/Content.Shared/_NF/Smuggling/Prototypes/SmugglingReportMessageSetPrototype.cs b/Content.Shared/_NF/Smuggling/Prototypes/SmugglingReportMessageSetPrototype.cs index 78a97fa727a..d79d7d56e34 100644 --- a/Content.Shared/_NF/Smuggling/Prototypes/SmugglingReportMessageSetPrototype.cs +++ b/Content.Shared/_NF/Smuggling/Prototypes/SmugglingReportMessageSetPrototype.cs @@ -5,7 +5,7 @@ namespace Content.Shared._NF.Smuggling.Prototypes; // Data types for the sending of smuggling messages over radio. [Prototype("smugglingReportMessageSet")] -public sealed class SmugglingReportMessageSetPrototype : IPrototype +public sealed partial class SmugglingReportMessageSetPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Content.Tests/Content.Tests.csproj b/Content.Tests/Content.Tests.csproj index ff295728fca..75a077839aa 100644 --- a/Content.Tests/Content.Tests.csproj +++ b/Content.Tests/Content.Tests.csproj @@ -1,26 +1,25 @@  - - $(TargetFramework) - 12 - false false ..\bin\Content.Tests\ + disable + + + + + + + - - - - - diff --git a/Content.Tests/ContentUnitTest.cs b/Content.Tests/ContentUnitTest.cs index 827c4e2f437..18938ac4ea4 100644 --- a/Content.Tests/ContentUnitTest.cs +++ b/Content.Tests/ContentUnitTest.cs @@ -2,8 +2,8 @@ using System.Reflection; using Content.Client.IoC; using Content.Server.IoC; -using Content.Shared.IoC; using Robust.Shared.Analyzers; +using Robust.Shared.IoC; using Robust.UnitTesting; using EntryPoint = Content.Server.Entry.EntryPoint; @@ -15,16 +15,15 @@ public class ContentUnitTest : RobustUnitTest protected override void OverrideIoC() { base.OverrideIoC(); - - SharedContentIoC.Register(); + var dependencies = IoCManager.Instance!; if (Project == UnitTestProject.Server) { - ServerContentIoC.Register(); + ServerContentIoC.Register(dependencies); } else if (Project == UnitTestProject.Client) { - ClientContentIoC.Register(); + ClientContentIoC.Register(dependencies); } } diff --git a/Content.Tools/Content.Tools.csproj b/Content.Tools/Content.Tools.csproj index f611924c581..06a9b790db2 100644 --- a/Content.Tools/Content.Tools.csproj +++ b/Content.Tools/Content.Tools.csproj @@ -1,16 +1,15 @@ Exe - $(TargetFramework) - - - + - + + + diff --git a/Content.YAMLLinter/Content.YAMLLinter.csproj b/Content.YAMLLinter/Content.YAMLLinter.csproj index 4e7344beaa2..bc2b7d1cb6b 100644 --- a/Content.YAMLLinter/Content.YAMLLinter.csproj +++ b/Content.YAMLLinter/Content.YAMLLinter.csproj @@ -1,21 +1,20 @@ Exe - $(TargetFramework) ..\bin\Content.YAMLLinter\ false true + false + disable + + - - - - @@ -23,4 +22,10 @@ + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index b19b4f5d592..342bb932bc7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,17 +6,17 @@ --> - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - \ No newline at end of file + diff --git a/MSBuild/Content.props b/MSBuild/Content.props new file mode 100644 index 00000000000..7216961fa0c --- /dev/null +++ b/MSBuild/Content.props @@ -0,0 +1,12 @@ + + + + + 14 + false + enable + + diff --git a/Pow3r/Pow3r.csproj b/Pow3r/Pow3r.csproj index 4f2d20087ab..20181910b46 100644 --- a/Pow3r/Pow3r.csproj +++ b/Pow3r/Pow3r.csproj @@ -1,10 +1,12 @@  Exe - $(TargetFramework) true + disable + + @@ -14,9 +16,10 @@ - - + + + diff --git a/Resources/Locale/en-US/job/role-ban-command.ftl b/Resources/Locale/en-US/job/role-ban-command.ftl index 26062c25b7f..148fb9a7b07 100644 --- a/Resources/Locale/en-US/job/role-ban-command.ftl +++ b/Resources/Locale/en-US/job/role-ban-command.ftl @@ -44,7 +44,6 @@ cmd-roleban-severity-parse = ${severity} is not a valid severity\n{$help}. cmd-roleban-arg-count = Invalid amount of arguments. cmd-roleban-job-parse = Job {$job} does not exist. cmd-roleban-name-parse = Unable to find a player with that name. -cmd-roleban-existing = {$target} already has a role ban for {$role}. cmd-roleban-success = Role banned {$target} from {$role} with reason {$reason} {$length}. cmd-roleban-inf = permanently diff --git a/Resources/Locale/ru-RU/_Lua/_test/version.ftl b/Resources/Locale/ru-RU/_Lua/_test/version.ftl index 0c725e7f25c..7a15155ab20 100644 --- a/Resources/Locale/ru-RU/_Lua/_test/version.ftl +++ b/Resources/Locale/ru-RU/_Lua/_test/version.ftl @@ -1,4 +1,4 @@ -connecting-version = Beta 268_v2.1 +connecting-version = Beta 272_v2.2a server-status-stable = Сервер стабилен server-status-medium = Средняя нагрузка server-status-high = Высокая нагрузка diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 4aa300132ff..b2626e4e922 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -136,6 +136,7 @@ - fuck - replay_recording_start - replay_recording_stop + - transfer_test - Flags: QUERY Commands: diff --git a/RobustToolbox b/RobustToolbox index dbde8023ed2..f509405022c 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit dbde8023ed256ac69ae4b419422c936f78361653 +Subproject commit f509405022cf75c3a906b2e1bd0a3e8e7eafe3bc diff --git a/SpaceStation14.sln b/SpaceStation14.sln deleted file mode 100644 index 0e00fe5b12f..00000000000 --- a/SpaceStation14.sln +++ /dev/null @@ -1,494 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34330.188 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Shared", "Content.Shared\Content.Shared.csproj", "{26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Server", "Content.Server\Content.Server.csproj", "{B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}" - ProjectSection(ProjectDependencies) = postProject - {59250BAF-0000-0000-0000-000000000000} = {59250BAF-0000-0000-0000-000000000000} - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5} = {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Client", "Content.Client\Content.Client.csproj", "{A2E5F175-78AF-4DDD-8F97-E2D2552372ED}" - ProjectSection(ProjectDependencies) = postProject - {59250BAF-0000-0000-0000-000000000000} = {59250BAF-0000-0000-0000-000000000000} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Checker", "Build Checker", "{3202E94D-E985-4181-9F69-F458A7F6574F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildChecker", "BuildChecker\BuildChecker.csproj", "{C899FCA4-7037-4E49-ABC2-44DE72487110}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Tests", "Content.Tests\Content.Tests.csproj", "{8EDF4429-251A-416D-BB68-93F227191BCF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RobustToolbox", "RobustToolbox", "{83B4CBBA-547A-42F0-A7CD-8A67D93196CE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lidgren.Network", "RobustToolbox\Lidgren.Network\Lidgren.Network.csproj", "{59250BAF-0000-0000-0000-000000000000}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Client", "RobustToolbox\Robust.Client\Robust.Client.csproj", "{83429BD6-6358-4B18-BE51-401DF8EA2673}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Server", "RobustToolbox\Robust.Server\Robust.Server.csproj", "{B04AAE71-0000-0000-0000-000000000000}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Shared.Maths", "RobustToolbox\Robust.Shared.Maths\Robust.Shared.Maths.csproj", "{93F23A82-00C5-4572-964E-E7C9457726D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.UnitTesting", "RobustToolbox\Robust.UnitTesting\Robust.UnitTesting.csproj", "{F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Shared", "RobustToolbox\Robust.Shared\Robust.Shared.csproj", "{0529F740-0000-0000-0000-000000000000}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.IntegrationTests", "Content.IntegrationTests\Content.IntegrationTests.csproj", "{AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Benchmarks", "Content.Benchmarks\Content.Benchmarks.csproj", "{7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenToolkit.GraphicsLibraryFramework", "RobustToolbox\OpenToolkit.GraphicsLibraryFramework\OpenToolkit.GraphicsLibraryFramework.csproj", "{4809F412-3132-419E-BF9D-CCF7593C3533}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Server.Database", "Content.Server.Database\Content.Server.Database.csproj", "{45C9B43F-305D-4651-9863-F6384CBC847F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MSBuild", "MSBuild", "{50404922-9637-4394-BF59-165D0850ADC8}" - ProjectSection(SolutionItems) = preProject - RobustToolbox\MSBuild\Robust.Analyzers.targets = RobustToolbox\MSBuild\Robust.Analyzers.targets - RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets = RobustToolbox\MSBuild\Robust.CompNetworkGenerator.targets - RobustToolbox\MSBuild\Robust.Configurations.props = RobustToolbox\MSBuild\Robust.Configurations.props - RobustToolbox\MSBuild\Robust.DefineConstants.targets = RobustToolbox\MSBuild\Robust.DefineConstants.targets - RobustToolbox\MSBuild\Robust.Engine.props = RobustToolbox\MSBuild\Robust.Engine.props - RobustToolbox\MSBuild\Robust.Engine.targets = RobustToolbox\MSBuild\Robust.Engine.targets - RobustToolbox\MSBuild\Robust.Engine.Version.props = RobustToolbox\MSBuild\Robust.Engine.Version.props - RobustToolbox\MSBuild\Robust.Platform.props = RobustToolbox\MSBuild\Robust.Platform.props - RobustToolbox\MSBuild\Robust.Properties.targets = RobustToolbox\MSBuild\Robust.Properties.targets - RobustToolbox\MSBuild\Robust.Trimming.targets = RobustToolbox\MSBuild\Robust.Trimming.targets - RobustToolbox\MSBuild\XamlIL.targets = RobustToolbox\MSBuild\XamlIL.targets - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{806ED41A-411B-4B3B-BEB6-DEC6DCA4C205}" - ProjectSection(SolutionItems) = preProject - Tools\generate_hashes.ps1 = Tools\generate_hashes.ps1 - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Robust.Shared.Scripting", "RobustToolbox\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj", "{41B450C0-A361-4CD7-8121-7072B8995CFC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSerializer", "RobustToolbox\NetSerializer\NetSerializer\NetSerializer.csproj", "{7B9472D3-79D4-48D1-9B22-BCDE518FE842}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.LoaderApi", "RobustToolbox\Robust.LoaderApi\Robust.LoaderApi\Robust.LoaderApi.csproj", "{1FAE651D-29D8-437A-9864-47CE0D180016}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Client.NameGenerator", "RobustToolbox\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj", "{3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Client.Injectors", "RobustToolbox\Robust.Client.Injectors\Robust.Client.Injectors.csproj", "{8922428F-17C3-47A7-BFE9-570DEB2464DA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.IL.Cecil", "RobustToolbox\XamlX\src\XamlX.IL.Cecil\XamlX.IL.Cecil.csproj", "{16F7DE32-0186-44B9-9345-0C20D1BF2422}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "XamlX", "XamlX", "{AFF53804-115F-4E67-B81F-26265EA27880}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX", "RobustToolbox\XamlX\src\XamlX\XamlX.csproj", "{23F09C45-950E-4DB7-A465-E937450FF008}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlX.Runtime", "RobustToolbox\XamlX\src\XamlX.Runtime\XamlX.Runtime.csproj", "{440426C1-8DCA-43F6-967F-94439B8DAF47}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Tools", "Content.Tools\Content.Tools.csproj", "{75AB8F8D-9E56-4B12-85E3-E03A852B31CC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Analyzers", "RobustToolbox\Robust.Analyzers\Robust.Analyzers.csproj", "{88B0FC0F-7209-40E2-AF16-EB90AF727C5B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.YAMLLinter", "Content.YAMLLinter\Content.YAMLLinter.csproj", "{A59E2FCC-93CF-4886-8EA7-94F021A7475D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "RobustToolbox\Avalonia.Base\Avalonia.Base.csproj", "{A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Benchmarks", "RobustToolbox\Robust.Benchmarks\Robust.Benchmarks.csproj", "{8A21C7CA-2EB8-40E5-8043-33582C06D139}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pow3r", "Pow3r\Pow3r.csproj", "{1C048C9F-00A9-4796-BE4D-BB36B7769720}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Shared.Database", "Content.Shared.Database\Content.Shared.Database.csproj", "{8842381D-3426-4BA8-93DA-599AB14D88E9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.MapRenderer", "Content.MapRenderer\Content.MapRenderer.csproj", "{199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Packaging", "RobustToolbox\Robust.Packaging\Robust.Packaging.csproj", "{952AAF2A-DF63-4A7D-8094-3453893EBA80}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Packaging", "Content.Packaging\Content.Packaging.csproj", "{424445D4-F5D9-4CA9-A435-0A36E8AA28F3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Files", "Project Files", "{EC27A16C-9412-4AA6-AE68-CAAB28882802}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - Directory.Packages.props = Directory.Packages.props - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Files", "Project Files", "{A965CB3B-FD31-44AF-8872-85ABA436098D}" - ProjectSection(SolutionItems) = preProject - RobustToolbox\Directory.Packages.props = RobustToolbox\Directory.Packages.props - RobustToolbox\README.md = RobustToolbox\README.md - RobustToolbox\RELEASE-NOTES.md = RobustToolbox\RELEASE-NOTES.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Content.Replay", "Content.Replay\Content.Replay.csproj", "{A493616C-338D-47B7-8072-A7F14D034D0B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Shared.CompNetworkGenerator", "RobustToolbox\Robust.Shared.CompNetworkGenerator\Robust.Shared.CompNetworkGenerator.csproj", "{07CA34A1-1D37-4771-A2E3-495A1044AE0B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Serialization.Generator", "RobustToolbox\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj", "{6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.PatreonParser", "Content.PatreonParser\Content.PatreonParser.csproj", "{D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Analyzers.Tests", "RobustToolbox\Robust.Analyzers.Tests\Robust.Analyzers.Tests.csproj", "{83F510FE-9B50-4D96-AFAB-CC13998D6AFE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Roslyn", "Roslyn", "{7844DA69-B0F0-49FB-A05E-ECA37372277A}" - ProjectSection(SolutionItems) = preProject - RobustToolbox\Robust.Roslyn.Shared\Robust.Roslyn.Shared.props = RobustToolbox\Robust.Roslyn.Shared\Robust.Roslyn.Shared.props - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Xaml", "RobustToolbox\Robust.Xaml\Robust.Xaml.csproj", "{5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - DebugOpt|Any CPU = DebugOpt|Any CPU - Release|Any CPU = Release|Any CPU - Tools|Any CPU = Tools|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Release|Any CPU.Build.0 = Release|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {26AEEBB3-DDE7-443A-9F43-7BC7F4ACF6B5}.Tools|Any CPU.Build.0 = Tools|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Release|Any CPU.Build.0 = Release|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {B38DBBD0-04C2-4D1A-84E2-B3446F6ADF2A}.Tools|Any CPU.Build.0 = Tools|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Release|Any CPU.Build.0 = Release|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {A2E5F175-78AF-4DDD-8F97-E2D2552372ED}.Tools|Any CPU.Build.0 = Tools|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {C899FCA4-7037-4E49-ABC2-44DE72487110}.Tools|Any CPU.Build.0 = Tools|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Release|Any CPU.Build.0 = Release|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {8EDF4429-251A-416D-BB68-93F227191BCF}.Tools|Any CPU.Build.0 = Tools|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59250BAF-0000-0000-0000-000000000000}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {59250BAF-0000-0000-0000-000000000000}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Release|Any CPU.Build.0 = Release|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {59250BAF-0000-0000-0000-000000000000}.Tools|Any CPU.Build.0 = Tools|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Release|Any CPU.Build.0 = Release|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {83429BD6-6358-4B18-BE51-401DF8EA2673}.Tools|Any CPU.Build.0 = Tools|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Release|Any CPU.Build.0 = Release|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {B04AAE71-0000-0000-0000-000000000000}.Tools|Any CPU.Build.0 = Tools|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Release|Any CPU.Build.0 = Release|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {93F23A82-00C5-4572-964E-E7C9457726D4}.Tools|Any CPU.Build.0 = Tools|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Release|Any CPU.Build.0 = Release|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9}.Tools|Any CPU.Build.0 = Tools|Any CPU - {0529F740-0000-0000-0000-000000000000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0529F740-0000-0000-0000-000000000000}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0529F740-0000-0000-0000-000000000000}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {0529F740-0000-0000-0000-000000000000}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {0529F740-0000-0000-0000-000000000000}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0529F740-0000-0000-0000-000000000000}.Release|Any CPU.Build.0 = Release|Any CPU - {0529F740-0000-0000-0000-000000000000}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {0529F740-0000-0000-0000-000000000000}.Tools|Any CPU.Build.0 = Tools|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Release|Any CPU.Build.0 = Release|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {AB7AF1C8-30FF-4436-9DF0-179BE5B0C132}.Tools|Any CPU.Build.0 = Tools|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Release|Any CPU.Build.0 = Release|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {7AC832A1-2461-4EB5-AC26-26F6AFFA9E46}.Tools|Any CPU.Build.0 = Tools|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Release|Any CPU.Build.0 = Release|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {4809F412-3132-419E-BF9D-CCF7593C3533}.Tools|Any CPU.Build.0 = Tools|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|Any CPU.Build.0 = Release|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {45C9B43F-305D-4651-9863-F6384CBC847F}.Tools|Any CPU.Build.0 = Tools|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Release|Any CPU.Build.0 = Release|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {41B450C0-A361-4CD7-8121-7072B8995CFC}.Tools|Any CPU.Build.0 = Tools|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Release|Any CPU.Build.0 = Release|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {7B9472D3-79D4-48D1-9B22-BCDE518FE842}.Tools|Any CPU.Build.0 = Release|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Release|Any CPU.Build.0 = Release|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {1FAE651D-29D8-437A-9864-47CE0D180016}.Tools|Any CPU.Build.0 = Release|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Release|Any CPU.Build.0 = Release|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA}.Tools|Any CPU.Build.0 = Release|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Release|Any CPU.Build.0 = Release|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {8922428F-17C3-47A7-BFE9-570DEB2464DA}.Tools|Any CPU.Build.0 = Release|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Debug|Any CPU.Build.0 = Debug|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Release|Any CPU.Build.0 = Release|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {16F7DE32-0186-44B9-9345-0C20D1BF2422}.Tools|Any CPU.Build.0 = Release|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Release|Any CPU.Build.0 = Release|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {23F09C45-950E-4DB7-A465-E937450FF008}.Tools|Any CPU.Build.0 = Release|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Release|Any CPU.Build.0 = Release|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {440426C1-8DCA-43F6-967F-94439B8DAF47}.Tools|Any CPU.Build.0 = Release|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Release|Any CPU.Build.0 = Release|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {75AB8F8D-9E56-4B12-85E3-E03A852B31CC}.Tools|Any CPU.Build.0 = Tools|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Release|Any CPU.Build.0 = Release|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B}.Tools|Any CPU.Build.0 = Release|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Release|Any CPU.Build.0 = Release|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {A59E2FCC-93CF-4886-8EA7-94F021A7475D}.Tools|Any CPU.Build.0 = Tools|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Release|Any CPU.Build.0 = Release|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Tools|Any CPU.ActiveCfg = Release|Any CPU - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12}.Tools|Any CPU.Build.0 = Release|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Release|Any CPU.Build.0 = Release|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {8A21C7CA-2EB8-40E5-8043-33582C06D139}.Tools|Any CPU.Build.0 = Tools|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Release|Any CPU.Build.0 = Release|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {1C048C9F-00A9-4796-BE4D-BB36B7769720}.Tools|Any CPU.Build.0 = Tools|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Release|Any CPU.Build.0 = Release|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {8842381D-3426-4BA8-93DA-599AB14D88E9}.Tools|Any CPU.Build.0 = Tools|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Release|Any CPU.Build.0 = Release|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {199BBEA1-7627-434B-B6F6-0F52A7C0E1E0}.Tools|Any CPU.Build.0 = Tools|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Release|Any CPU.Build.0 = Release|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {952AAF2A-DF63-4A7D-8094-3453893EBA80}.Tools|Any CPU.Build.0 = Tools|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Release|Any CPU.Build.0 = Release|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {424445D4-F5D9-4CA9-A435-0A36E8AA28F3}.Tools|Any CPU.Build.0 = Tools|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Release|Any CPU.Build.0 = Release|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {A493616C-338D-47B7-8072-A7F14D034D0B}.Tools|Any CPU.Build.0 = Tools|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Release|Any CPU.Build.0 = Release|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {07CA34A1-1D37-4771-A2E3-495A1044AE0B}.Tools|Any CPU.Build.0 = Tools|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Release|Any CPU.Build.0 = Release|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.ActiveCfg = Debug|Any CPU - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2}.Tools|Any CPU.Build.0 = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Release|Any CPU.Build.0 = Release|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.ActiveCfg = Debug|Any CPU - {D97D8258-D915-4D1D-B1E3-1A8D00CF9EB5}.Tools|Any CPU.Build.0 = Debug|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.DebugOpt|Any CPU.ActiveCfg = DebugOpt|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.DebugOpt|Any CPU.Build.0 = DebugOpt|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Release|Any CPU.Build.0 = Release|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Tools|Any CPU.ActiveCfg = Tools|Any CPU - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE}.Tools|Any CPU.Build.0 = Tools|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.DebugOpt|Any CPU.ActiveCfg = Debug|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.DebugOpt|Any CPU.Build.0 = Debug|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Release|Any CPU.Build.0 = Release|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Tools|Any CPU.ActiveCfg = Debug|Any CPU - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6}.Tools|Any CPU.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C899FCA4-7037-4E49-ABC2-44DE72487110} = {3202E94D-E985-4181-9F69-F458A7F6574F} - {59250BAF-0000-0000-0000-000000000000} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {83429BD6-6358-4B18-BE51-401DF8EA2673} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {B04AAE71-0000-0000-0000-000000000000} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {93F23A82-00C5-4572-964E-E7C9457726D4} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {F0ADA779-40B8-4F7E-BA6C-CDB19F3065D9} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {0529F740-0000-0000-0000-000000000000} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {4809F412-3132-419E-BF9D-CCF7593C3533} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {50404922-9637-4394-BF59-165D0850ADC8} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {41B450C0-A361-4CD7-8121-7072B8995CFC} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {7B9472D3-79D4-48D1-9B22-BCDE518FE842} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {1FAE651D-29D8-437A-9864-47CE0D180016} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {8922428F-17C3-47A7-BFE9-570DEB2464DA} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {16F7DE32-0186-44B9-9345-0C20D1BF2422} = {AFF53804-115F-4E67-B81F-26265EA27880} - {AFF53804-115F-4E67-B81F-26265EA27880} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {23F09C45-950E-4DB7-A465-E937450FF008} = {AFF53804-115F-4E67-B81F-26265EA27880} - {440426C1-8DCA-43F6-967F-94439B8DAF47} = {AFF53804-115F-4E67-B81F-26265EA27880} - {A3C5B00A-D232-4A01-B82E-B0E58BFD5C12} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {8A21C7CA-2EB8-40E5-8043-33582C06D139} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {952AAF2A-DF63-4A7D-8094-3453893EBA80} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {A965CB3B-FD31-44AF-8872-85ABA436098D} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {7844DA69-B0F0-49FB-A05E-ECA37372277A} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - {3CFEB7DB-12C6-46F3-89FC-1450F3016FFA} = {7844DA69-B0F0-49FB-A05E-ECA37372277A} - {6FBF108E-5CB5-47DE-8D7E-B496ABA9E3E2} = {7844DA69-B0F0-49FB-A05E-ECA37372277A} - {07CA34A1-1D37-4771-A2E3-495A1044AE0B} = {7844DA69-B0F0-49FB-A05E-ECA37372277A} - {88B0FC0F-7209-40E2-AF16-EB90AF727C5B} = {7844DA69-B0F0-49FB-A05E-ECA37372277A} - {83F510FE-9B50-4D96-AFAB-CC13998D6AFE} = {7844DA69-B0F0-49FB-A05E-ECA37372277A} - {5C05B9B4-6AFE-4884-AA6A-5A26C0FFF2F6} = {83B4CBBA-547A-42F0-A7CD-8A67D93196CE} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {AA37ED9F-F8D6-468E-A101-658AD605B09A} - EndGlobalSection -EndGlobal diff --git a/SpaceStation14.slnx b/SpaceStation14.slnx new file mode 100644 index 00000000000..5fdcaafd735 --- /dev/null +++ b/SpaceStation14.slnx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.slnx.DotSettings similarity index 100% rename from SpaceStation14.sln.DotSettings rename to SpaceStation14.slnx.DotSettings diff --git a/Tools/InspectInlineArray/InspectInlineArray.csproj b/Tools/InspectInlineArray/InspectInlineArray.csproj new file mode 100644 index 00000000000..ed9781c223a --- /dev/null +++ b/Tools/InspectInlineArray/InspectInlineArray.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/Tools/InspectInlineArray/Program.cs b/Tools/InspectInlineArray/Program.cs new file mode 100644 index 00000000000..436dd5a07c4 --- /dev/null +++ b/Tools/InspectInlineArray/Program.cs @@ -0,0 +1,598 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Reflection.Emit; + +namespace InspectInlineArray; + +internal readonly record struct TypeMark(string Display, bool HasInlineArray); + +internal sealed class TypeMarkProvider : ISignatureTypeProvider +{ + private readonly MetadataReader _reader; + + public TypeMarkProvider(MetadataReader reader) + { + _reader = reader; + } + + public TypeMark GetArrayType(TypeMark elementType, ArrayShape shape) => + new($"{elementType.Display}[{new string(',', shape.Rank - 1)}]", elementType.HasInlineArray); + + public TypeMark GetByReferenceType(TypeMark elementType) => + new($"{elementType.Display}&", elementType.HasInlineArray); + + public TypeMark GetFunctionPointerType(MethodSignature signature) => + new("fnptr", signature.ReturnType.HasInlineArray || signature.ParameterTypes.Any(p => p.HasInlineArray)); + + public TypeMark GetGenericInstantiation(TypeMark genericType, ImmutableArray typeArguments) => + new($"{genericType.Display}<{string.Join(", ", typeArguments.Select(a => a.Display))}>", + genericType.HasInlineArray || typeArguments.Any(a => a.HasInlineArray)); + + public TypeMark GetGenericMethodParameter(object? genericContext, int index) => new($"!!{index}", false); + + public TypeMark GetGenericTypeParameter(object? genericContext, int index) => new($"!{index}", false); + + public TypeMark GetModifiedType(TypeMark modifier, TypeMark unmodifiedType, bool isRequired) => + new(unmodifiedType.Display, modifier.HasInlineArray || unmodifiedType.HasInlineArray); + + public TypeMark GetPinnedType(TypeMark elementType) => new($"{elementType.Display} pinned", elementType.HasInlineArray); + + public TypeMark GetPointerType(TypeMark elementType) => new($"{elementType.Display}*", elementType.HasInlineArray); + + public TypeMark GetPrimitiveType(PrimitiveTypeCode typeCode) => new(typeCode.ToString(), false); + + public TypeMark GetSZArrayType(TypeMark elementType) => new($"{elementType.Display}[]", elementType.HasInlineArray); + + public TypeMark GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + var def = reader.GetTypeDefinition(handle); + var ns = reader.GetString(def.Namespace); + var name = reader.GetString(def.Name); + var full = string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + var inline = name.StartsWith("<>y__InlineArray", StringComparison.Ordinal); + return new(full, inline); + } + + public TypeMark GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + var tr = reader.GetTypeReference(handle); + var ns = reader.GetString(tr.Namespace); + var name = reader.GetString(tr.Name); + var full = string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + return new(full, false); + } + + public TypeMark GetTypeFromSpecification(MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + var ts = reader.GetTypeSpecification(handle); + var decoder = new SignatureDecoder(this, reader, genericContext); + var br = reader.GetBlobReader(ts.Signature); + return decoder.DecodeType(ref br); + } +} + +internal static class Program +{ + private static readonly Dictionary OpCodeMap = BuildOpCodeMap(); + + private static Dictionary BuildOpCodeMap() + { + var dict = new Dictionary(); + foreach (var f in typeof(OpCodes).GetFields().Where(f => f.FieldType == typeof(OpCode))) + { + var op = (OpCode) f.GetValue(null)!; + dict[(ushort) op.Value] = op; + } + + return dict; + } + + private static int OperandSize(byte[] il, int operandStart, OperandType operandType) + { + return operandType switch + { + OperandType.InlineNone => 0, + OperandType.ShortInlineBrTarget => 1, + OperandType.ShortInlineI => 1, + OperandType.ShortInlineVar => 1, + OperandType.InlineVar => 2, + OperandType.InlineI => 4, + OperandType.InlineBrTarget => 4, + OperandType.InlineField => 4, + OperandType.InlineMethod => 4, + OperandType.InlineSig => 4, + OperandType.InlineString => 4, + OperandType.InlineTok => 4, + OperandType.InlineType => 4, + OperandType.ShortInlineR => 4, + OperandType.InlineI8 => 8, + OperandType.InlineR => 8, + OperandType.InlineSwitch => 4 + BitConverter.ToInt32(il, operandStart) * 4, + _ => 0 + }; + } + + private static string FormatMethodRef(MetadataReader reader, EntityHandle handle) + { + try + { + if (handle.Kind == HandleKind.MemberReference) + { + var mr = reader.GetMemberReference((MemberReferenceHandle) handle); + return $"{GetFullTypeName(reader, mr.Parent)}.{reader.GetString(mr.Name)}"; + } + + if (handle.Kind == HandleKind.MethodDefinition) + { + var md = reader.GetMethodDefinition((MethodDefinitionHandle) handle); + return $"{GetFullTypeName(reader, md.GetDeclaringType())}.{reader.GetString(md.Name)}"; + } + } + catch + { + // ignored + } + + return $"<{handle.Kind}>"; + } + + private static IEnumerable GuessInlineArrayConsumers(PEReader pe, MetadataReader reader, string typeName, string methodName) + { + // Find method handle + MethodDefinitionHandle? targetHandle = null; + + foreach (var tdHandle in reader.TypeDefinitions) + { + if (GetFullTypeNameDef(reader, tdHandle) != typeName) + continue; + + var td = reader.GetTypeDefinition(tdHandle); + foreach (var mdHandle in td.GetMethods()) + { + var md = reader.GetMethodDefinition(mdHandle); + if (reader.GetString(md.Name) == methodName) + { + targetHandle = mdHandle; + break; + } + } + } + + if (targetHandle == null) + yield break; + + var target = reader.GetMethodDefinition(targetHandle.Value); + if (target.RelativeVirtualAddress == 0) + yield break; + + var typeProvider = new TypeMarkProvider(reader); + + // Find type handles for <>y__InlineArray* (generic type definitions) + var inlineArrayTypeDefs = new HashSet(); + foreach (var tdHandle in reader.TypeDefinitions) + { + var td = reader.GetTypeDefinition(tdHandle); + var name = reader.GetString(td.Name); + if (name.StartsWith("<>y__InlineArray", StringComparison.Ordinal)) + inlineArrayTypeDefs.Add(tdHandle); + } + + var body = pe.GetMethodBody(target.RelativeVirtualAddress); + var il = body.GetILBytes(); + + var pendingCalls = 0; + var consumers = new HashSet(StringComparer.Ordinal); + + for (var i = 0; i < il.Length;) + { + ushort opVal; + int opSize; + + if (il[i] == 0xFE && i + 1 < il.Length) + { + opVal = (ushort) (0xFE00 | il[i + 1]); + opSize = 2; + } + else + { + opVal = il[i]; + opSize = 1; + } + + if (!OpCodeMap.TryGetValue(opVal, out var op)) + { + i += opSize; + continue; + } + + var operandStart = i + opSize; + var operandLen = OperandSize(il, operandStart, op.OperandType); + + // If instruction references a type token, see if it's our inline array (direct or via TypeSpec) + if (op.OperandType is OperandType.InlineType or OperandType.InlineTok) + { + if (operandStart + 4 <= il.Length) + { + var token = BitConverter.ToInt32(il, operandStart); + var h = MetadataTokens.EntityHandle(token); + if (h.Kind == HandleKind.TypeDefinition && inlineArrayTypeDefs.Contains((TypeDefinitionHandle) h)) + { + pendingCalls = 8; + } + else if (h.Kind == HandleKind.TypeSpecification) + { + var ts = reader.GetTypeSpecification((TypeSpecificationHandle) h); + var br = reader.GetBlobReader(ts.Signature); + var dec = new SignatureDecoder(typeProvider, reader, genericContext: null); + var decoded = dec.DecodeType(ref br); + if (decoded.HasInlineArray) + pendingCalls = 8; + } + } + } + + // initobj uses InlineType but shows up as OpCode 0xFE15 (we already handle) + // stfld/ldfld can reference inline array element fields too, via InlineField. + if (op.OperandType == OperandType.InlineField && operandStart + 4 <= il.Length) + { + var token = BitConverter.ToInt32(il, operandStart); + var h = MetadataTokens.EntityHandle(token); + if (h.Kind == HandleKind.FieldDefinition) + { + var fd = reader.GetFieldDefinition((FieldDefinitionHandle) h); + var parent = fd.GetDeclaringType(); + if (inlineArrayTypeDefs.Contains(parent)) + pendingCalls = 8; + } + else if (h.Kind == HandleKind.MemberReference) + { + var mr = reader.GetMemberReference((MemberReferenceHandle) h); + if (mr.Parent.Kind == HandleKind.TypeDefinition && inlineArrayTypeDefs.Contains((TypeDefinitionHandle) mr.Parent)) + { + pendingCalls = 8; + } + else if (mr.Parent.Kind == HandleKind.TypeSpecification) + { + var ts = reader.GetTypeSpecification((TypeSpecificationHandle) mr.Parent); + var br = reader.GetBlobReader(ts.Signature); + var dec = new SignatureDecoder(typeProvider, reader, genericContext: null); + var decoded = dec.DecodeType(ref br); + if (decoded.HasInlineArray) + pendingCalls = 8; + } + } + } + + if (op.OperandType == OperandType.InlineMethod && operandStart + 4 <= il.Length) + { + var token = BitConverter.ToInt32(il, operandStart); + var h = MetadataTokens.EntityHandle(token); + + if (pendingCalls > 0) + { + consumers.Add(FormatMethodRef(reader, h)); + pendingCalls--; + } + } + + i = operandStart + operandLen; + } + + foreach (var c in consumers.OrderBy(x => x, StringComparer.Ordinal)) + yield return c; + } + + private static string? TryFindRepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null) + { + if (Directory.Exists(Path.Combine(dir.FullName, "Content.Client")) && + Directory.Exists(Path.Combine(dir.FullName, "RobustToolbox"))) + { + return dir.FullName; + } + + dir = dir.Parent; + } + + return null; + } + + private static string DefaultAssemblyPath() + { + var root = TryFindRepoRoot(); + if (root == null) + throw new InvalidOperationException("Could not locate repo root (expected Content.Client/ and RobustToolbox/). Pass assembly path as first argument."); + + return Path.Combine(root, "bin", "Content.Client", "Content.Client.dll"); + } + + private static string GetFullTypeName(MetadataReader reader, EntityHandle typeHandle) + { + return typeHandle.Kind switch + { + HandleKind.TypeDefinition => GetFullTypeNameDef(reader, (TypeDefinitionHandle) typeHandle), + HandleKind.TypeReference => GetFullTypeNameRef(reader, (TypeReferenceHandle) typeHandle), + _ => $"<{typeHandle.Kind}>" + }; + } + + private static string GetFullTypeNameDef(MetadataReader reader, TypeDefinitionHandle handle) + { + var def = reader.GetTypeDefinition(handle); + + // Handle nested types by walking declaring-type chain. + var name = reader.GetString(def.Name); + var current = def; + while (!current.GetDeclaringType().IsNil) + { + var parentHandle = current.GetDeclaringType(); + var parent = reader.GetTypeDefinition(parentHandle); + name = $"{reader.GetString(parent.Name)}+{name}"; + current = parent; + } + + var ns = reader.GetString(current.Namespace); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + private static string GetFullTypeNameRef(MetadataReader reader, TypeReferenceHandle handle) + { + var tr = reader.GetTypeReference(handle); + var ns = reader.GetString(tr.Namespace); + var name = reader.GetString(tr.Name); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + private static string GetAttributeTypeName(MetadataReader reader, CustomAttribute attribute) + { + var ctor = attribute.Constructor; + + EntityHandle typeHandle = ctor.Kind switch + { + HandleKind.MemberReference => reader.GetMemberReference((MemberReferenceHandle) ctor).Parent, + HandleKind.MethodDefinition => reader.GetMethodDefinition((MethodDefinitionHandle) ctor).GetDeclaringType(), + _ => default + }; + + if (typeHandle.IsNil) + return ""; + + return GetFullTypeName(reader, typeHandle); + } + + private static bool HasPrivateImplInlineArrayHelper(MetadataReader reader) + { + foreach (var tdHandle in reader.TypeDefinitions) + { + var td = reader.GetTypeDefinition(tdHandle); + var name = reader.GetString(td.Name); + if (name != "") + continue; + + foreach (var mdHandle in td.GetMethods()) + { + var md = reader.GetMethodDefinition(mdHandle); + var methodName = reader.GetString(md.Name); + if (methodName.Contains("InlineArrayAsReadOnlySpan", StringComparison.Ordinal)) + return true; + } + + return false; + } + + return false; + } + + private static bool IsInlineArrayAsReadOnlySpanCall(MetadataReader reader, int metadataToken) + { + try + { + var handle = MetadataTokens.EntityHandle(metadataToken); + if (handle.Kind is not (HandleKind.MemberReference or HandleKind.MethodDefinition)) + return false; + + string methodName; + EntityHandle declaringType; + + if (handle.Kind == HandleKind.MemberReference) + { + var mr = reader.GetMemberReference((MemberReferenceHandle) handle); + methodName = reader.GetString(mr.Name); + declaringType = mr.Parent; + } + else + { + var md = reader.GetMethodDefinition((MethodDefinitionHandle) handle); + methodName = reader.GetString(md.Name); + declaringType = md.GetDeclaringType(); + } + + if (!methodName.Contains("InlineArrayAsReadOnlySpan", StringComparison.Ordinal)) + return false; + + var typeName = GetFullTypeName(reader, declaringType); + return typeName == ""; + } + catch + { + return false; + } + } + + private static IEnumerable<(string Method, int Offset)> FindInlineArrayHelperCalls(PEReader pe, MetadataReader reader) + { + foreach (var tdHandle in reader.TypeDefinitions) + { + var typeName = GetFullTypeNameDef(reader, tdHandle); + var td = reader.GetTypeDefinition(tdHandle); + + foreach (var mdHandle in td.GetMethods()) + { + var md = reader.GetMethodDefinition(mdHandle); + var rva = md.RelativeVirtualAddress; + if (rva == 0) + continue; // abstract / extern + + var body = pe.GetMethodBody(rva); + var il = body.GetILBytes(); + + // Fast heuristic scan: + // 0x28 = call, 0x6F = callvirt, both followed by 4-byte metadata token. + for (var i = 0; i + 4 < il.Length; i++) + { + var op = il[i]; + if (op != 0x28 && op != 0x6F) + continue; + + var token = il[i + 1] | (il[i + 2] << 8) | (il[i + 3] << 16) | (il[i + 4] << 24); + if (!IsInlineArrayAsReadOnlySpanCall(reader, token)) + continue; + + var methodName = reader.GetString(md.Name); + yield return ($"{typeName}.{methodName}", i); + } + } + } + } + + public static int Main(string[] args) + { + var assemblyPath = args.Length > 0 ? args[0] : DefaultAssemblyPath(); + assemblyPath = Path.GetFullPath(assemblyPath); + + if (!File.Exists(assemblyPath)) + { + Console.Error.WriteLine($"Assembly not found: {assemblyPath}"); + return 2; + } + + Console.WriteLine($"Inspecting: {assemblyPath}"); + + using var stream = File.OpenRead(assemblyPath); + using var pe = new PEReader(stream); + var reader = pe.GetMetadataReader(); + + Console.WriteLine($"Has .InlineArrayAsReadOnlySpan: {HasPrivateImplInlineArrayHelper(reader)}"); + + const string InlineArrayAttributeName = "System.Runtime.CompilerServices.InlineArrayAttribute"; + + var inlineArrayTypes = new List(); + foreach (var tdHandle in reader.TypeDefinitions) + { + var td = reader.GetTypeDefinition(tdHandle); + foreach (var caHandle in td.GetCustomAttributes()) + { + var ca = reader.GetCustomAttribute(caHandle); + var attrType = GetAttributeTypeName(reader, ca); + if (attrType == InlineArrayAttributeName) + { + inlineArrayTypes.Add(GetFullTypeNameDef(reader, tdHandle)); + break; + } + } + } + + Console.WriteLine($"Types with [{InlineArrayAttributeName}]: {inlineArrayTypes.Count}"); + foreach (var t in inlineArrayTypes.OrderBy(x => x, StringComparer.Ordinal)) + { + Console.WriteLine($" - {t}"); + } + + var referencesInlineArray = reader.TypeReferences.Any(trHandle => + { + var tr = reader.GetTypeReference(trHandle); + var ns = reader.GetString(tr.Namespace); + var name = reader.GetString(tr.Name); + return ns == "System.Runtime.CompilerServices" && name == "InlineArrayAttribute"; + }); + + Console.WriteLine($"References InlineArrayAttribute type: {referencesInlineArray}"); + + var callSites = FindInlineArrayHelperCalls(pe, reader).Distinct().ToList(); + Console.WriteLine($"Methods calling InlineArrayAsReadOnlySpan: {callSites.Count}"); + foreach (var (method, offset) in callSites.OrderBy(x => x.Method, StringComparer.Ordinal)) + { + Console.WriteLine($" - {method} (IL+0x{offset:X})"); + } + + var provider = new TypeMarkProvider(reader); + var inlineArrayUsers = new Dictionary>(StringComparer.Ordinal); + + foreach (var tdHandle in reader.TypeDefinitions) + { + var td = reader.GetTypeDefinition(tdHandle); + var typeName = GetFullTypeNameDef(reader, tdHandle); + + foreach (var mdHandle in td.GetMethods()) + { + var md = reader.GetMethodDefinition(mdHandle); + var methodName = reader.GetString(md.Name); + var fullMethod = $"{typeName}.{methodName}"; + + var sig = md.DecodeSignature(provider, genericContext: null); + if (sig.ReturnType.HasInlineArray || sig.ParameterTypes.Any(p => p.HasInlineArray)) + { + if (!inlineArrayUsers.TryGetValue(fullMethod, out var set)) + inlineArrayUsers[fullMethod] = set = new HashSet(StringComparer.Ordinal); + + if (sig.ReturnType.HasInlineArray) + set.Add($"return: {sig.ReturnType.Display}"); + + foreach (var p in sig.ParameterTypes.Where(p => p.HasInlineArray)) + set.Add($"param: {p.Display}"); + } + + var rva = md.RelativeVirtualAddress; + if (rva == 0) + continue; + + var body = pe.GetMethodBody(rva); + if (body.LocalSignature.IsNil) + continue; + + var ss = reader.GetStandaloneSignature(body.LocalSignature); + var locals = ss.DecodeLocalSignature(provider, genericContext: null); + foreach (var l in locals.Where(l => l.HasInlineArray)) + { + if (!inlineArrayUsers.TryGetValue(fullMethod, out var set)) + inlineArrayUsers[fullMethod] = set = new HashSet(StringComparer.Ordinal); + set.Add($"local: {l.Display}"); + } + } + } + + Console.WriteLine($"Methods referencing <>y__InlineArray*: {inlineArrayUsers.Count}"); + foreach (var (m, details) in inlineArrayUsers.OrderBy(x => x.Key, StringComparer.Ordinal).Take(200)) + { + Console.WriteLine($" - {m}"); + foreach (var d in details.OrderBy(x => x, StringComparer.Ordinal)) + Console.WriteLine($" {d}"); + + // Best-effort: guess which call consumes the inline-array local. + // For state machine methods and large methods this is heuristic, but usually points at the culprit API. + var lastDot = m.LastIndexOf('.'); + if (lastDot > 0) + { + var tName = m.Substring(0, lastDot); + var mName = m.Substring(lastDot + 1); + var consumers = GuessInlineArrayConsumers(pe, reader, tName, mName).ToList(); + if (consumers.Count > 0) + { + Console.WriteLine(" consumers (heuristic):"); + foreach (var c in consumers.Take(10)) + Console.WriteLine($" - {c}"); + } + } + } + + return 0; + } +} diff --git a/Tools/dump_user_data.py b/Tools/dump_user_data.py index 39d23a9d3fd..d432778b815 100755 --- a/Tools/dump_user_data.py +++ b/Tools/dump_user_data.py @@ -8,7 +8,7 @@ import psycopg2 from uuid import UUID -LATEST_DB_MIGRATION = "20230725193102_AdminNotesImprovementsForeignKeys" +LATEST_DB_MIGRATION = "20260120200503_BanRefactor" def main(): parser = argparse.ArgumentParser() @@ -40,9 +40,8 @@ def main(): dump_play_time(cur, user_id, arg_output) dump_player(cur, user_id, arg_output) dump_preference(cur, user_id, arg_output) - dump_server_ban(cur, user_id, arg_output) + dump_ban(cur, user_id, arg_output) dump_server_ban_exemption(cur, user_id, arg_output) - dump_server_role_ban(cur, user_id, arg_output) dump_uploaded_resource_log(cur, user_id, arg_output) dump_whitelist(cur, user_id, arg_output) @@ -277,7 +276,7 @@ def dump_preference(cur: "psycopg2.cursor", user_id: str, outdir: str): f.write(json_data) -def dump_server_ban(cur: "psycopg2.cursor", user_id: str, outdir: str): +def dump_ban(cur: "psycopg2.cursor", user_id: str, outdir: str): print("Dumping server_ban...") cur.execute(""" @@ -287,19 +286,39 @@ def dump_server_ban(cur: "psycopg2.cursor", user_id: str, outdir: str): SELECT *, (SELECT to_jsonb(unban_sq) - 'ban_id' FROM ( - SELECT * FROM server_unban WHERE server_unban.ban_id = server_ban.server_ban_id + SELECT * FROM unban WHERE unban.ban_id = ban.ban_id ) unban_sq) - as unban + as unban, + (SELECT COALESCE(json_agg(to_jsonb(ban_player_subq) - 'ban_id'), '[]') FROM ( + SELECT * FROM ban_player WHERE ban_player.ban_id = ban.ban_id + ) ban_player_subq) + as ban_player, + (SELECT COALESCE(json_agg(to_jsonb(ban_address_subq) - 'ban_id'), '[]') FROM ( + SELECT * FROM ban_address WHERE ban_address.ban_id = ban.ban_id + ) ban_address_subq) + as ban_address, + (SELECT COALESCE(json_agg(to_jsonb(ban_role_subq) - 'ban_id'), '[]') FROM ( + SELECT * FROM ban_role WHERE ban_role.ban_id = ban.ban_id + ) ban_role_subq) + as ban_role, + (SELECT COALESCE(json_agg(to_jsonb(ban_hwid_subq) - 'ban_id'), '[]') FROM ( + SELECT * FROM ban_hwid WHERE ban_hwid.ban_id = ban.ban_id + ) ban_hwid_subq) + as ban_hwid, + (SELECT COALESCE(json_agg(to_jsonb(ban_round_subq) - 'ban_id'), '[]') FROM ( + SELECT * FROM ban_round WHERE ban_round.ban_id = ban.ban_id + ) ban_round_subq) + as ban_round FROM - server_ban + ban WHERE - player_user_id = %s + ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s) ) as data """, (user_id,)) json_data = cur.fetchall()[0][0] - with open(os.path.join(outdir, "server_ban.json"), "w", encoding="utf-8") as f: + with open(os.path.join(outdir, "ban.json"), "w", encoding="utf-8") as f: f.write(json_data) @@ -325,32 +344,6 @@ def dump_server_ban_exemption(cur: "psycopg2.cursor", user_id: str, outdir: str) f.write(json_data) -def dump_server_role_ban(cur: "psycopg2.cursor", user_id: str, outdir: str): - print("Dumping server_role_ban...") - - cur.execute(""" -SELECT - COALESCE(json_agg(to_json(data)), '[]') #>> '{}' -FROM ( - SELECT - *, - (SELECT to_jsonb(role_unban_sq) - 'ban_id' FROM ( - SELECT * FROM server_role_unban WHERE server_role_unban.ban_id = server_role_ban.server_role_ban_id - ) role_unban_sq) - as unban - FROM - server_role_ban - WHERE - player_user_id = %s -) as data -""", (user_id,)) - - json_data = cur.fetchall()[0][0] - - with open(os.path.join(outdir, "server_role_ban.json"), "w", encoding="utf-8") as f: - f.write(json_data) - - def dump_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str, outdir: str): print("Dumping uploaded_resource_log...") @@ -442,4 +435,3 @@ def dump_admin_watchlists(cur: "psycopg2.cursor", user_id: str, outdir: str): main() # "I'm surprised you managed to write this entire Python file without spamming the word 'sus' everywhere." - Remie - diff --git a/Tools/erase_user_data.py b/Tools/erase_user_data.py index 0cc1a31d93f..90de0baf6f3 100644 --- a/Tools/erase_user_data.py +++ b/Tools/erase_user_data.py @@ -12,7 +12,7 @@ import psycopg2 from uuid import UUID -LATEST_DB_MIGRATION = "20250211131539_LoadoutNames" +LATEST_DB_MIGRATION = "20250314222016_ConstructionFavorites" def main(): parser = argparse.ArgumentParser() @@ -38,9 +38,8 @@ def main(): clear_play_time(cur, user_id) clear_player(cur, user_id) clear_preference(cur, user_id) - clear_server_ban(cur, user_id) + clear_ban(cur, user_id) clear_server_ban_exemption(cur, user_id) - clear_server_role_ban(cur, user_id) clear_uploaded_resource_log(cur, user_id) clear_whitelist(cur, user_id) clear_blacklist(cur, user_id) @@ -144,14 +143,14 @@ def clear_preference(cur: "psycopg2.cursor", user_id: str): """, (user_id,)) -def clear_server_ban(cur: "psycopg2.cursor", user_id: str): - print("Clearing server_ban...") +def clear_ban(cur: "psycopg2.cursor", user_id: str): + print("Clearing ban...") cur.execute(""" DELETE FROM - server_ban + ban WHERE - player_user_id = %s + ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s) """, (user_id,)) @@ -166,17 +165,6 @@ def clear_server_ban_exemption(cur: "psycopg2.cursor", user_id: str): """, (user_id,)) -def clear_server_role_ban(cur: "psycopg2.cursor", user_id: str): - print("Clearing server_role_ban...") - - cur.execute(""" -DELETE FROM - server_role_ban -WHERE - player_user_id = %s -""", (user_id,)) - - def clear_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str): print("Clearing uploaded_resource_log...") diff --git a/global.json b/global.json index cdbb589edad..512142d2bea 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.100", "rollForward": "latestFeature" } }