From 9ad8447d1318f24e4102ddb99d26834c98db9683 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 22 Dec 2025 02:24:24 +0100 Subject: [PATCH 01/17] v269.0.0 RT update - .NET 10 (#41855) * Make ServerPackaging automatically get extra server assemblies * Make the switch * Use Content.Server.deps.json instead * Remove debug * Rewrite Now recursively fetches dependencies from Content.Server Only copies dependencies not covered by Robust This removes the need to manually specify most of the dependencies, even the content ones! Also look at runtime key properly to figure out the proper dll name. This actually removes some assemblies that were duplicated between the main directory and assemblies (various Microsoft.Extensions stuff) * Fix test compile errors when updating dependencies Ran across this while updating dependencies on the RT .NET 10 update. Should be fine to merge immediately. * More .NET 10 prep * Convert to SLNX Hell yeah * slnx now has size-2 indents * Update SLNX with new RT system * Remove reference to RT test in toolshed test * Remove accidental usage of transitive RT dependencies * Move Robust project references to RobustApi * Update solution file * Fix warnings in pow3r * Fix nullable warnings in integration tests idk where these came from * gitignore binlog files * Fix transitive dependency warnings in Content.Benchmarks * Update slnx * Okay, the Robust API thing didn't pan out. New plan. It apparently broke clean builds, as the dependencies aren't in the project asset list or something anymore. I tried to fix this, but it seems impossible to do without relying on .NET SDK internals, as there's no point in the NuGet graph walk process that seems cleanly extensible. Instead let's just do the much dumber thing: a bunch of .props files for content to import. Hooray! This also means that I have to go through and *explicitly* disable transitive dependencies everywhere in RT. This thankfully isn't too hard. * Update RT to 269.0.0 * One last solution update * Fix more data definition issues * Update RT to 269.0.1 * Fix it again --------- Co-authored-by: DrSmugleaf --- .editorconfig | 2 +- .github/workflows/build-docfx.yml | 2 +- .github/workflows/build-map-renderer.yml | 2 +- .github/workflows/build-test-debug.yml | 2 +- .github/workflows/publish-testing.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test-packaging.yml | 2 +- .github/workflows/yaml-linter.yml | 2 +- .gitignore | 3 + BuildChecker/git_helper.py | 2 +- Content.Benchmarks/Content.Benchmarks.csproj | 23 +- Content.Client/Cargo/UI/BountyEntry.xaml.cs | 1 - Content.Client/Content.Client.csproj | 20 +- .../UI/LobbyCharacterPreviewPanel.xaml.cs | 1 - .../Content.IntegrationTests.csproj | 16 +- .../Tests/Commands/ObjectiveCommandsTest.cs | 2 +- .../Tests/Minds/MindTests.EntityDeletion.cs | 6 +- .../Tests/Toolshed/AdminTest.cs | 2 +- .../Tests/Toolshed/LocTest.cs | 2 +- .../Content.MapRenderer.csproj | 12 +- Content.Packaging/Content.Packaging.csproj | 8 +- Content.Packaging/DepsHandler.cs | 80 +++ Content.Packaging/ServerPackaging.cs | 48 +- .../Content.PatreonParser.csproj | 2 +- Content.Replay/Content.Replay.csproj | 13 +- .../Content.Server.Database.csproj | 7 +- .../Codewords/CodewordFactionPrototype.cs | 4 +- .../Codewords/CodewordGeneratorPrototype.cs | 4 +- Content.Server/Content.Server.csproj | 17 +- .../Commands/SetGamePresetCommand.cs | 3 +- .../JobWhitelist/JobWhitelistManager.cs | 6 +- .../Content.Shared.Database.csproj | 3 +- .../Actions/Components/ActionComponent.cs | 2 +- .../Chat/Prototypes/EmoteSoundsPrototype.cs | 2 +- .../Prototypes/InjectorModePrototype.cs | 145 +++++ Content.Shared/Content.Shared.csproj | 21 +- .../CrewManifest/SharedCrewManifestSystem.cs | 1 - Content.Shared/Lathe/LatheMessages.cs | 1 - .../Nutrition/Prototypes/EdiblePrototype.cs | 60 +++ .../Loadouts/LoadoutGroupPrototype.cs | 9 + .../SmartFridge/SmartFridgeComponent.cs | 2 +- Content.Tests/Content.Tests.csproj | 17 +- Content.Tools/Content.Tools.csproj | 9 +- Content.YAMLLinter/Content.YAMLLinter.csproj | 15 +- Directory.Packages.props | 10 +- MSBuild/Content.props | 12 + Pow3r/Pow3r.csproj | 9 +- RobustToolbox | 2 +- SpaceStation14.sln | 494 ------------------ SpaceStation14.slnx | 168 ++++++ ...ettings => SpaceStation14.slnx.DotSettings | 0 global.json | 2 +- 52 files changed, 627 insertions(+), 655 deletions(-) create mode 100644 Content.Packaging/DepsHandler.cs create mode 100644 Content.Shared/Chemistry/Prototypes/InjectorModePrototype.cs create mode 100644 Content.Shared/Nutrition/Prototypes/EdiblePrototype.cs create mode 100644 MSBuild/Content.props delete mode 100644 SpaceStation14.sln create mode 100644 SpaceStation14.slnx rename SpaceStation14.sln.DotSettings => SpaceStation14.slnx.DotSettings (100%) 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.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/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.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/Tests/Commands/ObjectiveCommandsTest.cs b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs index a77761a7d17..d430325e31b 100644 --- a/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs +++ b/Content.IntegrationTests/Tests/Commands/ObjectiveCommandsTest.cs @@ -54,7 +54,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/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/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.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/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/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/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/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs index a729a9cc2b5..b9397cbb045 100644 --- a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -13,7 +13,6 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Serilog; namespace Content.Server.Players.JobWhitelist; @@ -25,9 +24,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 +94,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); @@ -226,5 +227,6 @@ void IPostInjectInit.PostInject() _userDb.AddOnLoadPlayer(LoadData); _userDb.AddOnFinishLoad(FinishLoad); _userDb.AddOnPlayerDisconnect(ClientDisconnected); + _sawmill = _logManager.GetSawmill("job_whitelist"); } } 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/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/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/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/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/Preferences/Loadouts/LoadoutGroupPrototype.cs b/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs index 4291ecfb989..ebbe9b65122 100644 --- a/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs +++ b/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs @@ -11,6 +11,15 @@ public sealed partial class LoadoutGroupPrototype : IPrototype [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/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.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.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/RobustToolbox b/RobustToolbox index dbde8023ed2..c7ba63ed8e9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit dbde8023ed256ac69ae4b419422c936f78361653 +Subproject commit c7ba63ed8e9c8087b7a78bada55057ac7aa0812d 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/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" } } From b3462c709f9fba79c20e1de7a21952aa42e8cd90 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Wed, 4 Feb 2026 13:50:07 +0700 Subject: [PATCH 02/17] Building fix --- .../DeviceNetworkingBenchmark.cs | 5 +- Content.IntegrationTests/Pair/TestPair.cs | 10 +-- .../PoolManager.Prototypes.cs | 3 +- Content.IntegrationTests/PoolManager.cs | 16 ++-- .../PoolTestLogHandler.cs | 79 ------------------- .../TestPrototypesAttribute.cs | 12 --- .../Tests/Access/AccessReaderTest.cs | 3 +- .../Tests/Atmos/AlarmThresholdTest.cs | 1 + .../Tests/Body/LegTest.cs | 3 +- .../Tests/Body/LungTest.cs | 8 +- .../Tests/Body/SaveLoadReparentTest.cs | 5 +- .../Tests/Buckle/BuckleTest.cs | 5 +- Content.IntegrationTests/Tests/CargoTest.cs | 8 +- .../Tests/Chemistry/SolutionRoundingTest.cs | 1 + .../Tests/Chemistry/SolutionSystemTests.cs | 1 + .../Tests/Chemistry/TryAllReactionsTest.cs | 5 +- .../Tests/Commands/ForceMapTest.cs | 1 + .../Tests/Commands/ObjectiveCommandsTest.cs | 3 +- .../Tests/Commands/RejuvenateTest.cs | 2 +- .../Tests/Commands/SuicideCommandTests.cs | 3 +- .../Tests/ContainerOcclusionTest.cs | 4 +- .../Tests/Damageable/DamageableTest.cs | 1 + .../DestructibleTestPrototypes.cs | 2 + .../Tests/DeviceLinking/DeviceLinkingTest.cs | 4 +- .../Tests/DeviceNetwork/DeviceNetworkTest.cs | 5 +- .../Tests/Disposal/DisposalUnitTest.cs | 5 +- .../Tests/DoAfter/DoAfterServerTest.cs | 1 + .../Tests/Doors/AirlockTest.cs | 4 +- .../Tests/Fluids/AbsorbentTest.cs | 1 + .../Components/ActionBlocking/HandCuffTest.cs | 1 + .../Tests/GameRules/FailAndStartPresetTest.cs | 1 + .../Tests/Gravity/WeightlessStatusTests.cs | 1 + .../Tests/GravityGridTest.cs | 1 + .../Tests/Hands/HandTests.cs | 3 +- .../Tests/HumanInventoryUniformSlotsTest.cs | 1 + .../Click/InteractionSystemTests.cs | 5 +- .../Tests/Interaction/InteractionTest.cs | 23 ++++-- .../Tests/Internals/AutoInternalsTests.cs | 1 + .../Tests/InventoryHelpersTest.cs | 1 + .../Tests/Linter/StaticFieldValidationTest.cs | 5 +- .../Tests/Minds/GhostRoleTests.cs | 6 +- .../Tests/Minds/MindTests.cs | 5 +- .../Tests/Power/PowerTest.cs | 3 +- .../Tests/Preferences/LoadoutTests.cs | 3 +- .../Tests/Preferences/ServerDbSqliteTests.cs | 9 +-- .../Tests/Round/JobTest.cs | 5 +- .../SmartFridge/SmartFridgeInteractionTest.cs | 1 + .../Tests/Station/StationJobsTest.cs | 5 +- .../Tests/Storage/EntityStorageTests.cs | 1 + Content.IntegrationTests/Tests/StoreTests.cs | 7 +- Content.IntegrationTests/Tests/Tag/TagTest.cs | 3 +- .../Utility/EntitySystemExtensionsTest.cs | 3 +- .../Tests/Utility/EntityWhitelistTest.cs | 4 +- .../Tests/Utility/SandboxTest.cs | 1 + .../Tests/Vending/VendingInteractionTest.cs | 3 +- .../Tests/VendingMachineRestockTest.cs | 3 +- .../Tests/Wires/WireLayoutTest.cs | 3 +- .../Tests/XenoArtifactTest.cs | 3 +- .../JobWhitelist/JobWhitelistManager.cs | 11 +-- .../Worldgen/Prototypes/GCQueuePrototype.cs | 13 ++- .../AiShuttle/AiShuttleSpawnRuleComponent.cs | 4 +- .../Components/BluespaceErrorRuleComponent.cs | 4 +- .../AnimatedLobbyScreenPrototype.cs | 4 +- Content.Shared/Audio/AmbientMusicPrototype.cs | 2 +- .../Corvax/TTS/TTSVoicePrototype.cs | 12 +-- .../Prototypes/CrispinessLevelSetPrototype.cs | 2 +- .../Loadouts/LoadoutGroupPrototype.cs | 3 +- Content.Shared/Roles/JobPrototype.cs | 2 +- .../SpaceBiomes/SpaceBiomePrototype.cs | 2 +- .../_DV/Mail/MailDeliveryPoolPrototype.cs | 4 +- .../_DV/Whitelist/WhitelistTierPrototype.cs | 4 +- .../Weapons/Ranged/TracerComponent.cs | 2 +- .../_Lua/Language/LanguagePrototype.cs | 4 +- .../SponsorLoadout/SponsorLoadoutPrototype.cs | 14 ++-- .../Cartridges/AppraisalUiState.cs | 2 +- .../Prototypes/SectorServicePrototypes.cs | 2 +- .../Shipyard/Prototypes/VesselPrototype.cs | 4 +- .../SmugglingReportMessageSetPrototype.cs | 2 +- 78 files changed, 178 insertions(+), 231 deletions(-) delete mode 100644 Content.IntegrationTests/PoolTestLogHandler.cs delete mode 100644 Content.IntegrationTests/TestPrototypesAttribute.cs 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.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..e5e23c350f7 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 { 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 d430325e31b..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; 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/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.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/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.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs index b9397cbb045..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,9 +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 System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace Content.Server.Players.JobWhitelist; @@ -154,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); @@ -191,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; 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/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/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/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/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/Preferences/Loadouts/LoadoutGroupPrototype.cs b/Content.Shared/Preferences/Loadouts/LoadoutGroupPrototype.cs index ebbe9b65122..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,7 +7,7 @@ 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; 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/_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/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!; From 318f8e8c00aa368e89c91584ab4bd00a4cc68f8f Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:42:39 -0800 Subject: [PATCH 03/17] Physics Assert in SharedMoverController (#37970) * Physics asserts and Xenoarch fixes * Fix blocking asserts * Alright ready for the test fails * Fix whitespace issues * Fix whitespace * Okay fix whitespace issues for real * Fix test fails * Temp fix * Fix * Whitespace * Added a big ass comment * Right * A * Should work * Debug performance * Mothership * fix test fails real * push * fix --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../Tests/Buckle/BuckleTest.cs | 2 ++ Content.Shared/Blocking/BlockingSystem.cs | 2 +- .../Friction/TileFrictionController.cs | 20 +++++++++-- .../Components/BlockMovementComponent.cs | 9 +++++ .../SharedInteractionSystem.Blocking.cs | 18 ++++++++-- .../Movement/Systems/SharedMoverController.cs | 33 ++++++++++++++++++- .../Entities/Mobs/Player/admin_ghost.yml | 14 ++++---- .../Entities/Mobs/Player/narsie.yml | 1 - .../Entities/Mobs/Player/ratvar.yml | 1 - .../Entities/Objects/Fun/immovable_rod.yml | 2 ++ .../Xenoarchaeology/item_xenoartifacts.yml | 2 +- .../structure_xenoartifacts.yml | 2 ++ .../Xenoarchaeology/xenoartifacts.yml | 2 +- 13 files changed, 89 insertions(+), 19 deletions(-) diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 502d221db82..c11b9b0fb81 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -32,6 +32,8 @@ public sealed partial class BuckleTest - type: Hands - type: ComplexInteraction - type: InputMover + - type: Physics + bodyType: KinematicController - type: Body prototype: Human - type: StandingState diff --git a/Content.Shared/Blocking/BlockingSystem.cs b/Content.Shared/Blocking/BlockingSystem.cs index 1465612e462..bc26112466f 100644 --- a/Content.Shared/Blocking/BlockingSystem.cs +++ b/Content.Shared/Blocking/BlockingSystem.cs @@ -247,7 +247,7 @@ public bool StopBlocking(EntityUid item, BlockingComponent component, EntityUid if (TryComp(user, out var blockingUserComponent) && TryComp(user, out var physicsComponent)) { if (xform.Anchored) - _transformSystem.Unanchor(user, xform); + _transformSystem.Unanchor(user, xform, false); _actionsSystem.SetToggled(component.BlockingToggleActionEntity, false); _fixtureSystem.DestroyFixture(user, BlockingComponent.BlockFixtureID, body: physicsComponent); diff --git a/Content.Shared/Friction/TileFrictionController.cs b/Content.Shared/Friction/TileFrictionController.cs index 25645139646..774a9a2b9ba 100644 --- a/Content.Shared/Friction/TileFrictionController.cs +++ b/Content.Shared/Friction/TileFrictionController.cs @@ -1,7 +1,9 @@ using System.Numerics; using Content.Shared.CCVar; using Content.Shared.Gravity; +using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Events; +using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; @@ -14,6 +16,7 @@ using Robust.Shared.Physics.Controllers; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Systems; +using Robust.Shared.Utility; namespace Content.Shared.Friction { @@ -26,11 +29,14 @@ public sealed class TileFrictionController : VirtualController [Dependency] private readonly SharedMapSystem _map = default!; private EntityQuery _frictionQuery; - private EntityQuery _xformQuery; private EntityQuery _pullerQuery; private EntityQuery _pullableQuery; private EntityQuery _gridQuery; + // For debug purposes only + private EntityQuery _moverQuery; + private EntityQuery _blockMoverQuery; + private float _frictionModifier; private float _minDamping; private float _airDamping; @@ -45,10 +51,11 @@ public override void Initialize() Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true); Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true); _frictionQuery = GetEntityQuery(); - _xformQuery = GetEntityQuery(); _pullerQuery = GetEntityQuery(); _pullableQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); + _moverQuery = GetEntityQuery(); + _blockMoverQuery = GetEntityQuery(); } public override void UpdateBeforeSolve(bool prediction, float frameTime) @@ -107,7 +114,16 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) PhysicsSystem.SetAngularDamping(uid, body, friction); if (body.BodyType != BodyType.KinematicController) + { + /* + * Extra catch for input movers that may be temporarily unable to move for whatever reason. + * Block movement shouldn't be added and removed frivolously so it should be reliable to use this + * as a check for brains and such which have input mover purely for ghosting behavior. + */ + DebugTools.Assert(!_moverQuery.HasComp(uid) || _blockMoverQuery.HasComp(uid), + $"Input mover: {ToPrettyString(uid)} in TileFrictionController is not the correct BodyType, BodyType found: {body.BodyType}, expected: KinematicController."); continue; + } // Physics engine doesn't apply damping to Kinematic Controllers so we have to do it here. // BEWARE YE TRAVELLER: diff --git a/Content.Shared/Interaction/Components/BlockMovementComponent.cs b/Content.Shared/Interaction/Components/BlockMovementComponent.cs index 2125f16efe0..a40e2b9e3cf 100644 --- a/Content.Shared/Interaction/Components/BlockMovementComponent.cs +++ b/Content.Shared/Interaction/Components/BlockMovementComponent.cs @@ -8,6 +8,15 @@ namespace Content.Shared.Interaction.Components; [RegisterComponent, NetworkedComponent] public sealed partial class BlockMovementComponent : Component { + /// + /// Blocks generic interactions such as container insertion, pick up, drop and such. + /// [DataField] public bool BlockInteraction = true; + + /// + /// Blocks being able to use entities. + /// + [DataField] + public bool BlockUse = true; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs index f6f4d8605d8..5d566b850e4 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs @@ -17,10 +17,10 @@ public partial class SharedInteractionSystem private void InitializeBlocking() { SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(CancelEvent); + SubscribeLocalEvent(CancelUseEvent); SubscribeLocalEvent(CancelInteractEvent); - SubscribeLocalEvent(CancelEvent); - SubscribeLocalEvent(CancelEvent); + SubscribeLocalEvent(CancellableInteractEvent); + SubscribeLocalEvent(CancellableInteractEvent); SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(OnBlockingStartup); @@ -33,6 +33,12 @@ private void CancelInteractEvent(Entity ent, ref Interac args.Cancelled = true; } + private void CancelUseEvent(Entity ent, ref UseAttemptEvent args) + { + if (ent.Comp.BlockUse) + args.Cancel(); + } + private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args) { // If we're relaying then don't cancel. @@ -42,6 +48,12 @@ private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, Upda args.Cancel(); // no more scurrying around } + private void CancellableInteractEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args) + { + if (component.BlockInteraction) + args.Cancel(); + } + private void CancelEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args) { args.Cancel(); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 25cdffd80ca..a7a22cb1ace 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -19,6 +19,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Controllers; +using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -97,6 +98,9 @@ public override void Initialize() NoShoesSilentQuery = GetEntityQuery(); // DeltaV - NoShoesSilentFootstepsComponent SubscribeLocalEvent(OnTileFriction); + SubscribeLocalEvent(OnMoverStartup); + SubscribeLocalEvent(OnPhysicsBodyChanged); + SubscribeLocalEvent(OnCanMove); InitializeInput(); InitializeRelay(); @@ -197,6 +201,21 @@ protected void HandleMobMovement( return; } + /* + * This assert is here because any entity using inputs to move should be a Kinematic Controller. + * Kinematic Controllers are not built to use the entirety of the Physics engine by intention and + * setting an input mover to Dynamic will cause the Physics engine to occasionally throw asserts. + * In addition, SharedMoverController applies its own forms of fake impulses and friction outside + * Physics simulation, which will cause issues for Dynamic bodies (Such as Friction being applied twice). + * Kinematic bodies have even less Physics options and as such aren't suitable for a player, especially + * when we move to Box2D v3 where there will be more support for players updating outside of simulation. + * Lastly, static bodies can't move so they shouldn't be updated. If a static body makes it here we're + * doing unnecessary calculations. + * Only a Kinematic Controller should be making it to this point. + */ + DebugTools.Assert(physicsComponent.BodyType == BodyType.KinematicController, + $"Input mover: {ToPrettyString(uid)} in HandleMobMovement is not the correct BodyType, BodyType found: {physicsComponent.BodyType}, expected: KinematicController."); + // If the body is in air but isn't weightless then it can't move var weightless = _gravity.IsWeightless(uid); var inAirHelpless = false; @@ -346,7 +365,7 @@ protected void HandleMobMovement( if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) && TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef)) { - var soundModifier = mover.Sprinting ? 3.5f : 1.5f; + var soundModifier = mover.Sprinting ? InputMoverComponent.SprintingSoundModifier : InputMoverComponent.WalkingSoundModifier; var audioParams = sound.Params .WithVolume(sound.Params.Volume + soundModifier) @@ -663,4 +682,16 @@ private void OnTileFriction(Entity ent, ref Tile else args.Modifier *= ent.Comp.BaseFriction; } + + private void OnPhysicsBodyChanged(Entity entity, ref PhysicsBodyTypeChangedEvent args) + { + _blocker.UpdateCanMove(entity); + } + + private void OnCanMove(Entity entity, ref UpdateCanMoveEvent args) + { + // If we don't have a physics component, or have a static body type then we can't move. + if (!PhysicsQuery.TryComp(entity, out var body) || body.BodyType == BodyType.Static) + args.Cancel(); + } } diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index d36fc0b6f02..6064cc9801c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -39,8 +39,6 @@ - type: CombatMode - type: Physics ignorePaused: true - bodyType: Kinematic - - type: Body - type: Access groups: - AllAccess @@ -240,8 +238,8 @@ - type: entity id: ActionAGhostShowStationAtmos - name: Мониторинг Атмосферной Сети - description: Используется для мониторинга атмосферных сетей станции + name: ���������� ����������� ���� + description: ������������ ��� ����������� ����������� ����� ������� components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: atmos } @@ -253,8 +251,8 @@ - type: entity id: ActionAGhostShowStationAtmosAlerts - name: Консоль Оповещений - description: Используется для доступа к автоматизированной системе оповещения станции + name: ������� ���������� + description: ������������ ��� ������� � ������������������ ������� ���������� ������� components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: atmosalert } @@ -266,8 +264,8 @@ - type: entity id: ActionAGhostShowStationPowerMonitor - name: Консоль Контроля Питания - description: Он отслеживает показатели энергии по всей станции + name: ������� �������� ������� + description: �� ����������� ���������� ������� �� ���� ������� components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: energy } diff --git a/Resources/Prototypes/Entities/Mobs/Player/narsie.yml b/Resources/Prototypes/Entities/Mobs/Player/narsie.yml index f2b81f796c7..e9bda23d15c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/narsie.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/narsie.yml @@ -84,7 +84,6 @@ - Syndicate globalReceive: true - type: Physics - bodyType: Dynamic bodyStatus: InAir - type: CanMoveInAir # singulose components diff --git a/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml b/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml index 658f14a6d40..2da87d0d34e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml @@ -80,7 +80,6 @@ - Syndicate globalReceive: true - type: Physics - bodyType: Dynamic bodyStatus: InAir - type: CanMoveInAir - type: EventHorizon diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index 15f67245f1b..5fe9a6a68e6 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -80,6 +80,8 @@ damage: types: Blunt: 190 + - type: Physics + bodyType: KinematicController - type: InputMover - type: MovementSpeedModifier baseWeightlessAcceleration: 5 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml index 65a4c498980..cd91be5377f 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml @@ -21,7 +21,7 @@ map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ] visible: false - type: Physics - bodyType: Dynamic + bodyType: KinematicController - type: CollisionWake enabled: false - type: InteractionOutline diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml index 8edbbd71f4a..7d85e375b21 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml @@ -19,6 +19,8 @@ - state: artifact-activation map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ] visible: false + - type: Physics + bodyType: KinematicController - type: RandomArtifactSprite maxSprite: 36 - type: RandomSprite diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml index 295d4c128fb..a0bef99e8ff 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml @@ -30,7 +30,7 @@ - type: Damageable - type: Actions - type: Physics - bodyType: Dynamic + bodyType: KinematicController - type: MovementSpeedModifier baseWalkSpeed: 0.25 baseSprintSpeed: 0.5 From 09cc5092a93279f1a9ca6226c1aaa4562a80a5b0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 24 Dec 2025 02:16:20 +0100 Subject: [PATCH 04/17] Update RT to v270.0.0 (#42029) Fix audio loading issues --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index c7ba63ed8e9..6d31d5ba240 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c7ba63ed8e9c8087b7a78bada55057ac7aa0812d +Subproject commit 6d31d5ba2404c9f069c456f10bd717929b00a9d8 From c82bcb81730d47eae8f0b89a990de0619ea260b9 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Wed, 4 Feb 2026 14:34:40 +0700 Subject: [PATCH 05/17] Revert "Physics Assert in SharedMoverController (#37970)" This reverts commit 318f8e8c00aa368e89c91584ab4bd00a4cc68f8f. --- .../Tests/Buckle/BuckleTest.cs | 2 -- Content.Shared/Blocking/BlockingSystem.cs | 2 +- .../Friction/TileFrictionController.cs | 20 ++--------- .../Components/BlockMovementComponent.cs | 9 ----- .../SharedInteractionSystem.Blocking.cs | 18 ++-------- .../Movement/Systems/SharedMoverController.cs | 33 +------------------ .../Entities/Mobs/Player/admin_ghost.yml | 14 ++++---- .../Entities/Mobs/Player/narsie.yml | 1 + .../Entities/Mobs/Player/ratvar.yml | 1 + .../Entities/Objects/Fun/immovable_rod.yml | 2 -- .../Xenoarchaeology/item_xenoartifacts.yml | 2 +- .../structure_xenoartifacts.yml | 2 -- .../Xenoarchaeology/xenoartifacts.yml | 2 +- 13 files changed, 19 insertions(+), 89 deletions(-) diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index c11b9b0fb81..502d221db82 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -32,8 +32,6 @@ public sealed partial class BuckleTest - type: Hands - type: ComplexInteraction - type: InputMover - - type: Physics - bodyType: KinematicController - type: Body prototype: Human - type: StandingState diff --git a/Content.Shared/Blocking/BlockingSystem.cs b/Content.Shared/Blocking/BlockingSystem.cs index bc26112466f..1465612e462 100644 --- a/Content.Shared/Blocking/BlockingSystem.cs +++ b/Content.Shared/Blocking/BlockingSystem.cs @@ -247,7 +247,7 @@ public bool StopBlocking(EntityUid item, BlockingComponent component, EntityUid if (TryComp(user, out var blockingUserComponent) && TryComp(user, out var physicsComponent)) { if (xform.Anchored) - _transformSystem.Unanchor(user, xform, false); + _transformSystem.Unanchor(user, xform); _actionsSystem.SetToggled(component.BlockingToggleActionEntity, false); _fixtureSystem.DestroyFixture(user, BlockingComponent.BlockFixtureID, body: physicsComponent); diff --git a/Content.Shared/Friction/TileFrictionController.cs b/Content.Shared/Friction/TileFrictionController.cs index 774a9a2b9ba..25645139646 100644 --- a/Content.Shared/Friction/TileFrictionController.cs +++ b/Content.Shared/Friction/TileFrictionController.cs @@ -1,9 +1,7 @@ using System.Numerics; using Content.Shared.CCVar; using Content.Shared.Gravity; -using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Events; -using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; @@ -16,7 +14,6 @@ using Robust.Shared.Physics.Controllers; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Systems; -using Robust.Shared.Utility; namespace Content.Shared.Friction { @@ -29,14 +26,11 @@ public sealed class TileFrictionController : VirtualController [Dependency] private readonly SharedMapSystem _map = default!; private EntityQuery _frictionQuery; + private EntityQuery _xformQuery; private EntityQuery _pullerQuery; private EntityQuery _pullableQuery; private EntityQuery _gridQuery; - // For debug purposes only - private EntityQuery _moverQuery; - private EntityQuery _blockMoverQuery; - private float _frictionModifier; private float _minDamping; private float _airDamping; @@ -51,11 +45,10 @@ public override void Initialize() Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true); Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true); _frictionQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); _pullerQuery = GetEntityQuery(); _pullableQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); - _moverQuery = GetEntityQuery(); - _blockMoverQuery = GetEntityQuery(); } public override void UpdateBeforeSolve(bool prediction, float frameTime) @@ -114,16 +107,7 @@ public override void UpdateBeforeSolve(bool prediction, float frameTime) PhysicsSystem.SetAngularDamping(uid, body, friction); if (body.BodyType != BodyType.KinematicController) - { - /* - * Extra catch for input movers that may be temporarily unable to move for whatever reason. - * Block movement shouldn't be added and removed frivolously so it should be reliable to use this - * as a check for brains and such which have input mover purely for ghosting behavior. - */ - DebugTools.Assert(!_moverQuery.HasComp(uid) || _blockMoverQuery.HasComp(uid), - $"Input mover: {ToPrettyString(uid)} in TileFrictionController is not the correct BodyType, BodyType found: {body.BodyType}, expected: KinematicController."); continue; - } // Physics engine doesn't apply damping to Kinematic Controllers so we have to do it here. // BEWARE YE TRAVELLER: diff --git a/Content.Shared/Interaction/Components/BlockMovementComponent.cs b/Content.Shared/Interaction/Components/BlockMovementComponent.cs index a40e2b9e3cf..2125f16efe0 100644 --- a/Content.Shared/Interaction/Components/BlockMovementComponent.cs +++ b/Content.Shared/Interaction/Components/BlockMovementComponent.cs @@ -8,15 +8,6 @@ namespace Content.Shared.Interaction.Components; [RegisterComponent, NetworkedComponent] public sealed partial class BlockMovementComponent : Component { - /// - /// Blocks generic interactions such as container insertion, pick up, drop and such. - /// [DataField] public bool BlockInteraction = true; - - /// - /// Blocks being able to use entities. - /// - [DataField] - public bool BlockUse = true; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs index 5d566b850e4..f6f4d8605d8 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs @@ -17,10 +17,10 @@ public partial class SharedInteractionSystem private void InitializeBlocking() { SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(CancelUseEvent); + SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(CancelInteractEvent); - SubscribeLocalEvent(CancellableInteractEvent); - SubscribeLocalEvent(CancellableInteractEvent); + SubscribeLocalEvent(CancelEvent); + SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(CancelEvent); SubscribeLocalEvent(OnBlockingStartup); @@ -33,12 +33,6 @@ private void CancelInteractEvent(Entity ent, ref Interac args.Cancelled = true; } - private void CancelUseEvent(Entity ent, ref UseAttemptEvent args) - { - if (ent.Comp.BlockUse) - args.Cancel(); - } - private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args) { // If we're relaying then don't cancel. @@ -48,12 +42,6 @@ private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, Upda args.Cancel(); // no more scurrying around } - private void CancellableInteractEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args) - { - if (component.BlockInteraction) - args.Cancel(); - } - private void CancelEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args) { args.Cancel(); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index a7a22cb1ace..25cdffd80ca 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -19,7 +19,6 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Controllers; -using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -98,9 +97,6 @@ public override void Initialize() NoShoesSilentQuery = GetEntityQuery(); // DeltaV - NoShoesSilentFootstepsComponent SubscribeLocalEvent(OnTileFriction); - SubscribeLocalEvent(OnMoverStartup); - SubscribeLocalEvent(OnPhysicsBodyChanged); - SubscribeLocalEvent(OnCanMove); InitializeInput(); InitializeRelay(); @@ -201,21 +197,6 @@ protected void HandleMobMovement( return; } - /* - * This assert is here because any entity using inputs to move should be a Kinematic Controller. - * Kinematic Controllers are not built to use the entirety of the Physics engine by intention and - * setting an input mover to Dynamic will cause the Physics engine to occasionally throw asserts. - * In addition, SharedMoverController applies its own forms of fake impulses and friction outside - * Physics simulation, which will cause issues for Dynamic bodies (Such as Friction being applied twice). - * Kinematic bodies have even less Physics options and as such aren't suitable for a player, especially - * when we move to Box2D v3 where there will be more support for players updating outside of simulation. - * Lastly, static bodies can't move so they shouldn't be updated. If a static body makes it here we're - * doing unnecessary calculations. - * Only a Kinematic Controller should be making it to this point. - */ - DebugTools.Assert(physicsComponent.BodyType == BodyType.KinematicController, - $"Input mover: {ToPrettyString(uid)} in HandleMobMovement is not the correct BodyType, BodyType found: {physicsComponent.BodyType}, expected: KinematicController."); - // If the body is in air but isn't weightless then it can't move var weightless = _gravity.IsWeightless(uid); var inAirHelpless = false; @@ -365,7 +346,7 @@ protected void HandleMobMovement( if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) && TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef)) { - var soundModifier = mover.Sprinting ? InputMoverComponent.SprintingSoundModifier : InputMoverComponent.WalkingSoundModifier; + var soundModifier = mover.Sprinting ? 3.5f : 1.5f; var audioParams = sound.Params .WithVolume(sound.Params.Volume + soundModifier) @@ -682,16 +663,4 @@ private void OnTileFriction(Entity ent, ref Tile else args.Modifier *= ent.Comp.BaseFriction; } - - private void OnPhysicsBodyChanged(Entity entity, ref PhysicsBodyTypeChangedEvent args) - { - _blocker.UpdateCanMove(entity); - } - - private void OnCanMove(Entity entity, ref UpdateCanMoveEvent args) - { - // If we don't have a physics component, or have a static body type then we can't move. - if (!PhysicsQuery.TryComp(entity, out var body) || body.BodyType == BodyType.Static) - args.Cancel(); - } } diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 6064cc9801c..d36fc0b6f02 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -39,6 +39,8 @@ - type: CombatMode - type: Physics ignorePaused: true + bodyType: Kinematic + - type: Body - type: Access groups: - AllAccess @@ -238,8 +240,8 @@ - type: entity id: ActionAGhostShowStationAtmos - name: ���������� ����������� ���� - description: ������������ ��� ����������� ����������� ����� ������� + name: Мониторинг Атмосферной Сети + description: Используется для мониторинга атмосферных сетей станции components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: atmos } @@ -251,8 +253,8 @@ - type: entity id: ActionAGhostShowStationAtmosAlerts - name: ������� ���������� - description: ������������ ��� ������� � ������������������ ������� ���������� ������� + name: Консоль Оповещений + description: Используется для доступа к автоматизированной системе оповещения станции components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: atmosalert } @@ -264,8 +266,8 @@ - type: entity id: ActionAGhostShowStationPowerMonitor - name: ������� �������� ������� - description: �� ����������� ���������� ������� �� ���� ������� + name: Консоль Контроля Питания + description: Он отслеживает показатели энергии по всей станции components: - type: Action icon: { sprite: _Lua/Interface/AdminActions.rsi, state: energy } diff --git a/Resources/Prototypes/Entities/Mobs/Player/narsie.yml b/Resources/Prototypes/Entities/Mobs/Player/narsie.yml index e9bda23d15c..f2b81f796c7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/narsie.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/narsie.yml @@ -84,6 +84,7 @@ - Syndicate globalReceive: true - type: Physics + bodyType: Dynamic bodyStatus: InAir - type: CanMoveInAir # singulose components diff --git a/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml b/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml index 2da87d0d34e..658f14a6d40 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/ratvar.yml @@ -80,6 +80,7 @@ - Syndicate globalReceive: true - type: Physics + bodyType: Dynamic bodyStatus: InAir - type: CanMoveInAir - type: EventHorizon diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index 5fe9a6a68e6..15f67245f1b 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -80,8 +80,6 @@ damage: types: Blunt: 190 - - type: Physics - bodyType: KinematicController - type: InputMover - type: MovementSpeedModifier baseWeightlessAcceleration: 5 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml index cd91be5377f..65a4c498980 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_xenoartifacts.yml @@ -21,7 +21,7 @@ map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ] visible: false - type: Physics - bodyType: KinematicController + bodyType: Dynamic - type: CollisionWake enabled: false - type: InteractionOutline diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml index 7d85e375b21..8edbbd71f4a 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_xenoartifacts.yml @@ -19,8 +19,6 @@ - state: artifact-activation map: [ "enum.ArtifactsVisualLayers.ActivationEffect" ] visible: false - - type: Physics - bodyType: KinematicController - type: RandomArtifactSprite maxSprite: 36 - type: RandomSprite diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml index a0bef99e8ff..295d4c128fb 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/xenoartifacts.yml @@ -30,7 +30,7 @@ - type: Damageable - type: Actions - type: Physics - bodyType: KinematicController + bodyType: Dynamic - type: MovementSpeedModifier baseWalkSpeed: 0.25 baseSprintSpeed: 0.5 From 9b786ab31867e07eaff92b714ec2b89b44c22c07 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 1 Jan 2026 21:34:05 +0100 Subject: [PATCH 06/17] Update RT to 270.1.0 (#42198) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 6d31d5ba240..68f8d00931d 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 6d31d5ba2404c9f069c456f10bd717929b00a9d8 +Subproject commit 68f8d00931d6b14f3e592d50c47dd44ef09eed1f From 764f7f6118c691f784b86df9031325b58838c3e8 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 20 Jan 2026 10:24:41 +0100 Subject: [PATCH 07/17] Update RT to 271.0.0 (#42533) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 68f8d00931d..65ed19fa4eb 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 68f8d00931d6b14f3e592d50c47dd44ef09eed1f +Subproject commit 65ed19fa4ebf1c4b4ddb2c8c491655f239c3e348 From 39ccdb74217f7d55d5c9d9bde8de942bf519670c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 20 Jan 2026 19:56:49 +0100 Subject: [PATCH 08/17] Update RT to 271.1.0 (#42551) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 65ed19fa4eb..40b10f0dccf 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 65ed19fa4ebf1c4b4ddb2c8c491655f239c3e348 +Subproject commit 40b10f0dccfe568ccd7dc3c6f6ee87a63bad97ee From bdac298df5467b1646e46241719fa7699aab4dae Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 25 Jan 2026 20:09:24 +0100 Subject: [PATCH 09/17] Update RT to 271.2.0 (#42646) --- Content.Server/Entry/EntryPoint.cs | 13 +++++++++++++ RobustToolbox | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 9f042a16c29..6559ef50185 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -38,6 +38,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; @@ -57,6 +58,18 @@ public sealed class EntryPoint : GameServer private IWatchlistWebhookManager _watchlistWebhookManager = default!; private IConnectionManager? _connectionManager; + 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() { diff --git a/RobustToolbox b/RobustToolbox index 40b10f0dccf..0b93a1b7e20 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 40b10f0dccfe568ccd7dc3c6f6ee87a63bad97ee +Subproject commit 0b93a1b7e20acaa4540061d084bf750dbcb1ae46 From d86efe3ed14fec70d73ea414eb547b38d4d168f5 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 29 Jan 2026 02:05:38 +0100 Subject: [PATCH 10/17] Update RT to 272.0.0 (#42694) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 0b93a1b7e20..f509405022c 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 0b93a1b7e20acaa4540061d084bf750dbcb1ae46 +Subproject commit f509405022cf75c3a906b2e1bd0a3e8e7eafe3bc From 026a06026d7ff93a825282c7fbaf6fe22a8e1f91 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Wed, 4 Feb 2026 15:30:24 +0700 Subject: [PATCH 11/17] patch maps --- .../Tabletop/TabletopCheckerSetup.cs | 34 +++++++---------- Content.Server/Tabletop/TabletopChessSetup.cs | 37 +++++++++---------- 2 files changed, 31 insertions(+), 40 deletions(-) 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; + } } } From 9ef9668cbd5057ee7b678cdeaa1a9bd0851ba0c6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 3 Oct 2025 03:32:56 +1300 Subject: [PATCH 12/17] Remove static IoC from client & server EntryPoint (#40562) * Remove static IoC from client & server EntryPoint * Fix missing log manager initialization * file namespace --- Content.Client/Entry/EntryPoint.cs | 11 +- Content.Client/IoC/ClientContentIoC.cs | 6 +- Content.Server/Entry/EntryPoint.cs | 245 ++++++++++++++----------- Content.Server/IoC/ServerContentIoC.cs | 108 +++++------ Content.Shared/Entry/EntryPoint.cs | 1 - Content.Shared/IoC/SharedContentIoC.cs | 6 +- Content.Tests/ContentUnitTest.cs | 9 +- 7 files changed, 209 insertions(+), 177 deletions(-) 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.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 6559ef50185..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; @@ -49,14 +47,48 @@ 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() { @@ -74,127 +106,123 @@ public override void PreInit() 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, _log.GetSawmill("configpreset")); - LoadConfigPresets(cfg, res, logManager.GetSawmill("configpreset")); + var aczProvider = new ContentMagicAczProvider(Dependencies); + _host.SetMagicAczProvider(aczProvider); - var aczProvider = new ContentMagicAczProvider(IoCManager.Resolve()); - IoCManager.Resolve().SetMagicAczProvider(aczProvider); + _factory.DoAutoRegistrations(); + _factory.IgnoreMissingComponents("Visuals"); + _factory.RegisterIgnore(IgnoredComponents.List); + _factory.GenerateNetIds(); - var factory = IoCManager.Resolve(); - var prototypes = IoCManager.Resolve(); + _proto.RegisterIgnore("parallax"); - factory.DoAutoRegistrations(); - factory.IgnoreMissingComponents("Visuals"); + _loc.Initialize(); - factory.RegisterIgnore(IgnoredComponents.List); - - prototypes.RegisterIgnore("parallax"); - - 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) @@ -204,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/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.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/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.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); } } From ad858c9146c302976b08717fec664026576844f6 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 23 Jan 2026 15:33:14 +0100 Subject: [PATCH 13/17] Ban database refactor (#42495) * Ban DB refactor seems to work at a basic level for PostgreSQL * New ban creation API Supports all the new functionality (multiple players/addresses/hwids/roles/rounds per ban). * Make the migration irreversible * Re-implement ban notifications The server ID check is no longer done as admins may want to place bans spanning multiple rounds irrelevant of the source server. * Fix some split query warnings * Implement migration on SQLite * More comments * Remove required from ban reason SS14.Admin changes would like this * More missing AsSplitQuery() calls * Fix missing ban type filter * Fix old CreateServerBan API with permanent time * Fix department and role ban commands with permanent time * Re-add banhits navigation property Dropped this on accident, SS14.Admin needs it. * More ban API fixes. * Don't fetch ban exemption info for role bans Not relevant, reduces query performance * Regenerate migrations * Fix adminnotes command for players that never connected Would blow up handling null player records. Not a new bug introduced by the refactor, but I ran into it. * Great shame... I accidentally committed submodule update... * Update GDPR scripts * Fix sandbox violation * Fix bans with duplicate info causing DB exceptions Most notably happened with role bans, as multiple departments may include the same role. --- .../Administration/UI/BanList/BanListEui.cs | 17 +- .../UI/BanList/Bans/BanListControl.xaml.cs | 2 +- .../UI/BanList/Bans/BanListLine.xaml.cs | 6 +- .../Administration/UI/BanList/IBanListLine.cs | 2 +- .../RoleBans/RoleBanListControl.xaml.cs | 2 +- .../BanList/RoleBans/RoleBanListLine.xaml.cs | 8 +- .../UI/Notes/AdminNotesLine.xaml.cs | 9 +- .../UI/Notes/AdminNotesLinePopup.xaml.cs | 4 +- .../JobRequirementsManager.cs | 16 +- .../Tests/Commands/PardonCommand.cs | 40 +- .../20260204104024_BanRefactor.Designer.cs | 2236 +++++++++++++++++ .../Postgres/20260204104024_BanRefactor.cs | 501 ++++ .../PostgresServerDbContextModelSnapshot.cs | 790 +++--- .../20260204104114_BanRefactor.Designer.cs | 2153 ++++++++++++++++ .../Sqlite/20260204104114_BanRefactor.cs | 496 ++++ .../SqliteServerDbContextModelSnapshot.cs | 762 +++--- Content.Server.Database/Model.Ban.cs | 328 +++ Content.Server.Database/Model.cs | 315 +-- Content.Server.Database/ModelPostgres.cs | 5 +- Content.Server.Database/ModelSqlite.cs | 8 +- .../Administration/BanList/BanListEui.cs | 80 +- Content.Server/Administration/BanPanelEui.cs | 61 +- .../Administration/Commands/BanCommand.cs | 10 +- .../Administration/Commands/BanListCommand.cs | 2 +- .../Commands/DepartmentBanCommand.cs | 106 +- .../Commands/OpenAdminNotesCommand.cs | 3 +- .../Administration/Commands/PardonCommand.cs | 4 +- .../Administration/Commands/RoleBanCommand.cs | 38 +- .../Commands/RoleBanListCommand.cs | 10 +- .../Managers/BanManager.Notification.cs | 18 +- .../Administration/Managers/BanManager.cs | 357 ++- .../Administration/Managers/IBanManager.cs | 367 ++- .../Administration/Notes/AdminNotesEui.cs | 8 +- .../Notes/AdminNotesExtensions.cs | 18 +- .../Administration/Notes/AdminNotesManager.cs | 25 +- .../Notes/IAdminNotesManager.cs | 3 +- .../Administration/PlayerPanelEui.cs | 7 +- .../Administration/Systems/BwoinkSystem.cs | 2 +- .../Connection/ConnectionManager.cs | 4 +- Content.Server/Database/BanDef.cs | 128 + Content.Server/Database/BanMatcher.cs | 50 +- Content.Server/Database/DatabaseRecords.cs | 53 +- Content.Server/Database/EFCoreExtensions.cs | 37 + Content.Server/Database/ServerBanDef.cs | 94 - Content.Server/Database/ServerDbBase.cs | 330 +-- Content.Server/Database/ServerDbManager.cs | 154 +- Content.Server/Database/ServerDbPostgres.cs | 379 +-- Content.Server/Database/ServerDbSqlite.cs | 298 +-- Content.Server/Database/ServerRoleBanDef.cs | 65 - Content.Server/Database/ServerRoleUnbanDef.cs | 19 - .../{ServerUnbanDef.cs => UnbanDef.cs} | 4 +- Content.Server/IP/IPAddressExt.cs | 16 +- .../Managers/VoteManager.DefaultVotes.cs | 19 +- Content.Shared.Database/Bans.cs | 33 + .../Administration/BanList/BanListEuiState.cs | 6 +- .../Administration/BanList/SharedBan.cs | 20 + .../Administration/BanList/SharedServerBan.cs | 17 - .../BanList/SharedServerRoleBan.cs | 18 - .../{SharedServerUnban.cs => SharedUnban.cs} | 2 +- .../Administration/Notes/SharedAdminNote.cs | 7 +- Content.Shared/Players/MsgRoleBans.cs | 28 +- .../Locale/en-US/job/role-ban-command.ftl | 1 - Resources/engineCommandPerms.yml | 1 + Tools/dump_user_data.py | 64 +- Tools/erase_user_data.py | 24 +- 65 files changed, 8028 insertions(+), 2662 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20260204104024_BanRefactor.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20260204104114_BanRefactor.cs create mode 100644 Content.Server.Database/Model.Ban.cs create mode 100644 Content.Server/Database/BanDef.cs create mode 100644 Content.Server/Database/EFCoreExtensions.cs delete mode 100644 Content.Server/Database/ServerBanDef.cs delete mode 100644 Content.Server/Database/ServerRoleBanDef.cs delete mode 100644 Content.Server/Database/ServerRoleUnbanDef.cs rename Content.Server/Database/{ServerUnbanDef.cs => UnbanDef.cs} (72%) create mode 100644 Content.Shared.Database/Bans.cs create mode 100644 Content.Shared/Administration/BanList/SharedBan.cs delete mode 100644 Content.Shared/Administration/BanList/SharedServerBan.cs delete mode 100644 Content.Shared/Administration/BanList/SharedServerRoleBan.cs rename Content.Shared/Administration/BanList/{SharedServerUnban.cs => SharedUnban.cs} (81%) 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/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.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.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/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/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/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/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.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/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/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/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/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/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...") From fa8240f169700001542d78d1d761e7a89d2bcf77 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Thu, 5 Feb 2026 11:11:55 +0700 Subject: [PATCH 14/17] spdx upd --- Content.IntegrationTests/Tests/_Lua/SPDXHunter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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; From ebf752a28c4f637f4318b1dab7d99f5db176fa83 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Thu, 5 Feb 2026 18:57:18 +0700 Subject: [PATCH 15/17] =?UTF-8?q?=D0=A1=D0=BE=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20=D1=8E=D0=BD=D0=B8=D1=82=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Parallax/Managers/ParallaxManager.cs | 7 +- .../Shuttles/UI/ShuttleNavControl.xaml.cs | 6 +- Content.Client/_Mono/Audio/AudioEchoSystem.cs | 16 +- .../Behaviors/EmptyAllContainersBehaviour.cs | 2 +- .../EntitySystems/SpawnOnDespawnSystem.cs | 3 +- Content.Shared/Fluids/SharedPuddleSystem.cs | 8 +- .../_NF/Roles/SharedJobTrackingSystem.cs | 8 +- .../InspectInlineArray.csproj | 10 + Tools/InspectInlineArray/Program.cs | 598 ++++++++++++++++++ 9 files changed, 646 insertions(+), 12 deletions(-) create mode 100644 Tools/InspectInlineArray/InspectInlineArray.csproj create mode 100644 Tools/InspectInlineArray/Program.cs 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/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.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/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.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/_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/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; + } +} From 6352504da31cc52f9122df4ae14452999cb8c96f Mon Sep 17 00:00:00 2001 From: HacksLua Date: Thu, 5 Feb 2026 19:28:17 +0700 Subject: [PATCH 16/17] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Content.IntegrationTests/Tests/Body/LungTest.cs | 1 - Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs | 1 - Content.IntegrationTests/Tests/Lobby/ServerReloginTest.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index e5e23c350f7..3d02d8e1515 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -48,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/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 From d5f4be8aaa51770743a2c976edf1f9e449752d66 Mon Sep 17 00:00:00 2001 From: HacksLua Date: Thu, 5 Feb 2026 19:43:17 +0700 Subject: [PATCH 17/17] 272_v2.2 alpha --- Resources/Locale/ru-RU/_Lua/_test/version.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = Высокая нагрузка