From b455ff5ad54e88f32837320fd4b9370ee8a27de0 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 24 Jun 2026 20:41:25 -0400 Subject: [PATCH] NEO-146: apply C# collection and using style fixes Replace .ToArray() with collection spread, simplify single-statement await using blocks, use Lock for Postgres schema gates, and document IDE0063/IDE0305/IDE0330/CA1859 conventions in csharp-style.md. --- .cursor/rules/csharp-style.md | 56 ++++++++++++++++++- ...otbarLoadoutPersistenceIntegrationTests.cs | 25 ++++----- ...ractInstancePersistenceIntegrationTests.cs | 32 ++++------- ...tractOutcomePersistenceIntegrationTests.cs | 47 ++++++---------- ...tionStandingPersistenceIntegrationTests.cs | 37 +++++------- .../InMemoryReputationDeltaStoreTests.cs | 2 +- ...utationDeltaPersistenceIntegrationTests.cs | 28 ++++------ ...NodeInstancePersistenceIntegrationTests.cs | 30 ++++------ ...ressionGrantPersistenceIntegrationTests.cs | 24 +++----- ...yerInventoryPersistenceIntegrationTests.cs | 42 ++++++-------- .../Mastery/MasteryCatalogRegistryTests.cs | 4 +- .../Game/Mastery/PerkStateApiTests.cs | 7 +-- .../PerkStatePersistenceIntegrationTests.cs | 43 ++++++-------- .../PostgresPositionStateIntegrationTests.cs | 23 +++----- ...uestProgressPersistenceIntegrationTests.cs | 48 +++++++--------- .../Quests/QuestDefinitionsWorldApiTests.cs | 4 +- .../Game/Quests/QuestProgressApiTests.cs | 5 +- ...ressionGrantPersistenceIntegrationTests.cs | 35 +++++------- .../PostgresHotbarLoadoutBootstrap.cs | 4 +- .../Combat/AbilityDefinitionCatalogLoader.cs | 5 +- .../Game/Combat/AbilityDefinitionRegistry.cs | 2 +- .../ContractTemplateCatalogLoader.cs | 5 +- .../Contracts/ContractTemplateRegistry.cs | 2 +- .../Contracts/InMemoryContractOutcomeStore.cs | 13 ++--- .../PostgresContractInstanceBootstrap.cs | 4 +- .../PostgresContractOutcomeBootstrap.cs | 4 +- .../Contracts/PostgresContractOutcomeStore.cs | 12 ++-- .../Crafting/RecipeDefinitionCatalogLoader.cs | 5 +- .../Game/Crafting/RecipeDefinitionRegistry.cs | 2 +- .../EncounterDefinitionCatalogLoader.cs | 5 +- .../Encounters/EncounterDefinitionRegistry.cs | 2 +- .../InMemoryEncounterCompleteEventStore.cs | 2 +- .../RewardTableDefinitionCatalogLoader.cs | 5 +- .../RewardTableDefinitionRegistry.cs | 2 +- .../FactionDefinitionCatalogLoader.cs | 5 +- .../Factions/FactionDefinitionRegistry.cs | 2 +- .../Factions/InMemoryReputationDeltaStore.cs | 7 +-- .../PostgresPlayerFactionStandingBootstrap.cs | 4 +- .../PostgresReputationDeltaBootstrap.cs | 4 +- .../PostgresResourceNodeInstanceBootstrap.cs | 4 +- .../Gathering/ResourceNodeCatalogLoader.cs | 10 ++-- .../Gigs/PostgresGigProgressionBootstrap.cs | 4 +- .../Game/Items/ItemDefinitionCatalogLoader.cs | 5 +- .../Game/Items/ItemDefinitionRegistry.cs | 2 +- .../Items/PostgresPlayerInventoryBootstrap.cs | 4 +- .../Game/Mastery/MasteryCatalogLoader.cs | 5 +- .../Mastery/PostgresPerkStateBootstrap.cs | 4 +- .../Npc/NpcBehaviorDefinitionCatalogLoader.cs | 5 +- .../Game/Npc/NpcBehaviorDefinitionRegistry.cs | 2 +- .../PostgresPositionBootstrap.cs | 11 ++-- .../PostgresPlayerQuestProgressBootstrap.cs | 4 +- .../Quests/PrototypeE7M2QuestCatalogRules.cs | 10 ++-- .../Quests/PrototypeE7M3QuestFactionRules.cs | 15 ++--- .../Quests/QuestDefinitionCatalogLoader.cs | 5 +- .../Game/Quests/QuestDefinitionRegistry.cs | 2 +- .../Rewards/InMemoryRewardDeliveryStore.cs | 6 +- .../PostgresSkillProgressionBootstrap.cs | 4 +- .../Skills/SkillDefinitionCatalogLoader.cs | 5 +- .../Game/Skills/SkillDefinitionRegistry.cs | 2 +- 59 files changed, 329 insertions(+), 368 deletions(-) diff --git a/.cursor/rules/csharp-style.md b/.cursor/rules/csharp-style.md index 4f3e4b9..1201727 100644 --- a/.cursor/rules/csharp-style.md +++ b/.cursor/rules/csharp-style.md @@ -54,7 +54,61 @@ if (string.IsNullOrEmpty(key)) - **File-scoped namespaces** (`namespace X;`) for new single-namespace files when the SDK/version allows. - **Pattern matching / nullability:** prefer modern C# features where they simplify code; honor **nullable reference type** annotations when the project enables them. -## Members +## Collection expressions (IDE0305) + +- Prefer **collection spread** (`[.. source]`) over **`.ToArray()`** / **`.ToList()`** when materializing a sequence into a new array or list for return values, record `with` copies, or local snapshots. +- Give an **explicit target type** when the compiler cannot infer one (CS9176), e.g. `string[] ids = [.. query.Select(static x => x.Id)];`. +- In **assertions**, avoid passing a bare spread directly to APIs with span overloads (e.g. `Assert.Equal`) — assign to a typed local first: + +```csharp +// Prefer +return [.. query]; +GrantedItems = [.. row.GrantedItems], + +// When type inference fails +string[] expectedOrder = [.. registry.GetDefinitionsInIdOrder().Select(static d => d.Id)]; +string[] actualOrder = [.. body.Quests.Select(static row => row.QuestId)]; +Assert.Equal(expectedOrder, actualOrder); + +// Avoid (ambiguous Assert overloads; unnecessary allocation helper) +Assert.Equal(expectedOrder, [.. body.Quests.Select(static row => row.QuestId)]); +return query.ToArray(); +``` + +## `using` / `await using` (IDE0063) + +- When a **`using` or `await using` block contains exactly one statement**, prefer the declaration form without braces: `await using var conn = …;` then the single statement on following lines **only if** it is still logically one resource scope (typical for `NpgsqlConnection` + one command in integration-test reset helpers). +- **Keep braced `using (var …) { … }`** when the block has **multiple statements** (Arrange + Act + Assert inside one scope, seed loops, or any sequence that shares locals). +- **Do not flatten** multi-statement blocks into `using var` at method scope when inner scopes reuse names like `store`, `registry`, or `scope` — that causes **CS0136** name clashes. Use distinct names (`seedStore`, `actScope`, `readStore`) or keep the braced block. + +```csharp +// Prefer — single disposable, single follow-on statement +await using var conn = new NpgsqlConnection(cs); +await conn.OpenAsync(); + +// Prefer — multiple statements in one scope +using (var firstScope = Factory.Services.CreateScope()) +{ + var store = firstScope.ServiceProvider.GetRequiredService(); + Assert.True(store.TryAppend(row)); +} + +// Avoid — flattens to method scope and collides with a later `var store` +using var firstScope = Factory.Services.CreateScope(); +var store = firstScope.ServiceProvider.GetRequiredService(); +Assert.True(store.TryAppend(row)); +// … later … +var store = secondScope.ServiceProvider.GetRequiredService(); // CS0136 +``` + +## Postgres schema gates (IDE0330) + +- In `*Bootstrap.cs` types, use **`System.Threading.Lock`** (not `object`) for static schema-initialization gates: `private static readonly Lock SchemaGate = new();`. + +## Concrete return types (CA1859) + +- When a private helper always returns a materialized array, prefer the **concrete array type** as the return type (e.g. `RewardItemGrantApplied[]`) instead of `IReadOnlyList` if callers only need the array and analyzers suggest it. + - Prefer **expression-bodied** members only when they stay one clear idea. - **LINQ:** favor readability over micro-chains; break complex queries across lines. diff --git a/server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs index 1ea4c61..3d838bc 100644 --- a/server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/AbilityInput/HotbarLoadoutPersistenceIntegrationTests.cs @@ -37,13 +37,12 @@ public sealed class HotbarLoadoutPersistenceIntegrationTests(PostgresIntegration HttpStatusCode postStatus; // Act - using (var firstClient = Factory.CreateClient()) - { - var post = await firstClient.PostAsJsonAsync( - "/game/players/dev-local-1/hotbar-loadout", - update); - postStatus = post.StatusCode; - } + using var firstClient = Factory.CreateClient(); + var post = await firstClient.PostAsJsonAsync( + "/game/players/dev-local-1/hotbar-loadout", + update); + postStatus = post.StatusCode; + // Assert await using var secondFactory = new PostgresWebApplicationFactory(); @@ -73,14 +72,10 @@ public sealed class HotbarLoadoutPersistenceIntegrationTests(PostgresIntegration var ddl = await File.ReadAllTextAsync(ddlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var apply = new NpgsqlCommand(ddl, conn)) - { - await apply.ExecuteNonQueryAsync(); - } + await using var apply = new NpgsqlCommand(ddl, conn); + await apply.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_hotbar_loadout;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_hotbar_loadout;", conn); + await truncate.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractInstancePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractInstancePersistenceIntegrationTests.cs index 1f9a5bd..f828ba4 100644 --- a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractInstancePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractInstancePersistenceIntegrationTests.cs @@ -138,14 +138,12 @@ public sealed class ContractInstancePersistenceIntegrationTests(PostgresIntegrat } ContractInstanceState readBack; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var store = secondScope.ServiceProvider.GetRequiredService(); - readBack = store.TryGet(PlayerId, InstanceId, out var snapshot) - ? snapshot - : null!; - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readStore = secondScope.ServiceProvider.GetRequiredService(); + readBack = readStore.TryGet(PlayerId, InstanceId, out var snapshot) + ? snapshot + : null!; // Assert Assert.NotNull(readBack); @@ -176,21 +174,15 @@ public sealed class ContractInstancePersistenceIntegrationTests(PostgresIntegrat var instanceDdl = await File.ReadAllTextAsync(instanceDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyInstance = new NpgsqlCommand(instanceDdl, conn)) - { - await applyInstance.ExecuteNonQueryAsync(); - } + await using var applyInstance = new NpgsqlCommand(instanceDdl, conn); + await applyInstance.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs index e85768d..11a386a 100644 --- a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs @@ -54,14 +54,12 @@ public sealed class ContractOutcomePersistenceIntegrationTests(PostgresIntegrati } ContractOutcomeRow readBack; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var outcomeStore = secondScope.ServiceProvider.GetRequiredService(); - readBack = outcomeStore.TryGetByIdempotencyKey(idempotencyKey, out var row) - ? row - : null!; - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readOutcomeStore = secondScope.ServiceProvider.GetRequiredService(); + readBack = readOutcomeStore.TryGetByIdempotencyKey(idempotencyKey, out var row) + ? row + : null!; // Assert Assert.NotNull(readBack); @@ -125,11 +123,10 @@ public sealed class ContractOutcomePersistenceIntegrationTests(PostgresIntegrati RecordedAt); // Act bool appended; - using (var scope = Factory.Services.CreateScope()) - { - var outcomeStore = scope.ServiceProvider.GetRequiredService(); - appended = outcomeStore.TryAppend(outcomeRow); - } + using var scope = Factory.Services.CreateScope(); + var outcomeStore = scope.ServiceProvider.GetRequiredService(); + appended = outcomeStore.TryAppend(outcomeRow); + // Assert Assert.False(appended); } @@ -158,26 +155,18 @@ public sealed class ContractOutcomePersistenceIntegrationTests(PostgresIntegrati var outcomeDdl = await File.ReadAllTextAsync(outcomeDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyInstance = new NpgsqlCommand(instanceDdl, conn)) - { - await applyInstance.ExecuteNonQueryAsync(); - } + await using var applyInstance = new NpgsqlCommand(instanceDdl, conn); + await applyInstance.ExecuteNonQueryAsync(); - await using (var applyOutcome = new NpgsqlCommand(outcomeDdl, conn)) - { - await applyOutcome.ExecuteNonQueryAsync(); - } + await using var applyOutcome = new NpgsqlCommand(outcomeDdl, conn); + await applyOutcome.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Factions/FactionStandingPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Factions/FactionStandingPersistenceIntegrationTests.cs index b276cba..2b69e04 100644 --- a/server/NeonSprawl.Server.Tests/Game/Factions/FactionStandingPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Factions/FactionStandingPersistenceIntegrationTests.cs @@ -31,12 +31,10 @@ public sealed class FactionStandingPersistenceIntegrationTests(PostgresIntegrati } FactionStandingReadOutcome readBack; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var store = secondScope.ServiceProvider.GetRequiredService(); - readBack = store.TryGetStanding(PlayerId, GridFactionId); - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readStore = secondScope.ServiceProvider.GetRequiredService(); + readBack = readStore.TryGetStanding(PlayerId, GridFactionId); // Assert Assert.True(writeOutcome.Success); @@ -69,11 +67,10 @@ public sealed class FactionStandingPersistenceIntegrationTests(PostgresIntegrati // Act FactionStandingReadOutcome readBack; - using (var scope = Factory.Services.CreateScope()) - { - var store = scope.ServiceProvider.GetRequiredService(); - readBack = store.TryGetStanding(PlayerId, GridFactionId); - } + using var scope = Factory.Services.CreateScope(); + var store = scope.ServiceProvider.GetRequiredService(); + readBack = store.TryGetStanding(PlayerId, GridFactionId); + // Assert Assert.True(readBack.Success); @@ -102,21 +99,15 @@ public sealed class FactionStandingPersistenceIntegrationTests(PostgresIntegrati var standingDdl = await File.ReadAllTextAsync(standingDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyStanding = new NpgsqlCommand(standingDdl, conn)) - { - await applyStanding.ExecuteNonQueryAsync(); - } + await using var applyStanding = new NpgsqlCommand(standingDdl, conn); + await applyStanding.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Factions/InMemoryReputationDeltaStoreTests.cs b/server/NeonSprawl.Server.Tests/Game/Factions/InMemoryReputationDeltaStoreTests.cs index 3f2a3d9..ab1506d 100644 --- a/server/NeonSprawl.Server.Tests/Game/Factions/InMemoryReputationDeltaStoreTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Factions/InMemoryReputationDeltaStoreTests.cs @@ -85,7 +85,7 @@ public sealed class InMemoryReputationDeltaStoreTests // Act var rows = store.GetDeltasForPlayer(PlayerId); // Assert - Assert.Equal(["delta-a", "delta-m", "delta-z"], rows.Select(static r => r.Id).ToArray()); + Assert.Equal(["delta-a", "delta-m", "delta-z"], [.. rows.Select(static r => r.Id)]); } [Fact] diff --git a/server/NeonSprawl.Server.Tests/Game/Factions/ReputationDeltaPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Factions/ReputationDeltaPersistenceIntegrationTests.cs index 50e512f..f69a434 100644 --- a/server/NeonSprawl.Server.Tests/Game/Factions/ReputationDeltaPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Factions/ReputationDeltaPersistenceIntegrationTests.cs @@ -40,12 +40,10 @@ public sealed class ReputationDeltaPersistenceIntegrationTests(PostgresIntegrati } IReadOnlyList readBack; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var store = secondScope.ServiceProvider.GetRequiredService(); - readBack = store.GetDeltasForPlayer(PlayerId); - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readStore = secondScope.ServiceProvider.GetRequiredService(); + readBack = readStore.GetDeltasForPlayer(PlayerId); // Assert var persisted = Assert.Single(readBack); @@ -77,21 +75,15 @@ public sealed class ReputationDeltaPersistenceIntegrationTests(PostgresIntegrati var auditDdl = await File.ReadAllTextAsync(auditDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyAudit = new NpgsqlCommand(auditDdl, conn)) - { - await applyAudit.ExecuteNonQueryAsync(); - } + await using var applyAudit = new NpgsqlCommand(auditDdl, conn); + await applyAudit.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs index eaa7c38..29f5ec6 100644 --- a/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs @@ -48,11 +48,11 @@ public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresInte await ResetInstanceTableAsync(); using (var seedScope = Factory.Services.CreateScope()) { - var registry = seedScope.ServiceProvider.GetRequiredService(); - var store = seedScope.ServiceProvider.GetRequiredService(); + var seedRegistry = seedScope.ServiceProvider.GetRequiredService(); + var seedStore = seedScope.ServiceProvider.GetRequiredService(); for (var i = 0; i < 10; i++) { - _ = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store); + _ = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, seedRegistry, seedStore); } } @@ -60,13 +60,11 @@ public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresInte ResourceNodeInstanceMutationOutcome denyOutcome; ResourceNodeInstanceSnapshot persisted; await using var secondFactory = new PostgresWebApplicationFactory(); - using (var scope = secondFactory.Services.CreateScope()) - { - var registry = scope.ServiceProvider.GetRequiredService(); - var store = scope.ServiceProvider.GetRequiredService(); - denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store); - store.TryGetRemainingGathers(AlphaId, out persisted); - } + using var actScope = secondFactory.Services.CreateScope(); + var actRegistry = actScope.ServiceProvider.GetRequiredService(); + var actStore = actScope.ServiceProvider.GetRequiredService(); + denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, actRegistry, actStore); + actStore.TryGetRemainingGathers(AlphaId, out persisted); // Assert Assert.Equal(ResourceNodeInstanceMutationKind.Denied, denyOutcome.Kind); @@ -92,14 +90,10 @@ public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresInte var ddl = await File.ReadAllTextAsync(ddlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var apply = new NpgsqlCommand(ddl, conn)) - { - await apply.ExecuteNonQueryAsync(); - } + await using var apply = new NpgsqlCommand(ddl, conn); + await apply.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE resource_node_instance;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE resource_node_instance;", conn); + await truncate.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Gigs/GigProgressionGrantPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Gigs/GigProgressionGrantPersistenceIntegrationTests.cs index 0d2dd62..8b89444 100644 --- a/server/NeonSprawl.Server.Tests/Game/Gigs/GigProgressionGrantPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Gigs/GigProgressionGrantPersistenceIntegrationTests.cs @@ -97,26 +97,18 @@ public sealed class GigProgressionGrantPersistenceIntegrationTests(PostgresInteg var gigDdl = await File.ReadAllTextAsync(gigDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyGig = new NpgsqlCommand(gigDdl, conn)) - { - await applyGig.ExecuteNonQueryAsync(); - } + await using var applyGig = new NpgsqlCommand(gigDdl, conn); + await applyGig.ExecuteNonQueryAsync(); - await using (var truncateGig = new NpgsqlCommand("TRUNCATE player_gig_progression;", conn)) - { - await truncateGig.ExecuteNonQueryAsync(); - } + await using var truncateGig = new NpgsqlCommand("TRUNCATE player_gig_progression;", conn); + await truncateGig.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryPersistenceIntegrationTests.cs index 37fcc29..17ec26b 100644 --- a/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryPersistenceIntegrationTests.cs @@ -54,31 +54,31 @@ public sealed class PlayerInventoryPersistenceIntegrationTests(PostgresIntegrati await ResetInventoryTableAsync(); using (var seedScope = Factory.Services.CreateScope()) { - var registry = seedScope.ServiceProvider.GetRequiredService(); - var store = seedScope.ServiceProvider.GetRequiredService(); + var seedRegistry = seedScope.ServiceProvider.GetRequiredService(); + var seedStore = seedScope.ServiceProvider.GetRequiredService(); for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++) { _ = PlayerInventoryOperations.TryAddStack( "dev-local-1", "survey_drone_kit", quantity: 1, - registry, - store); + seedRegistry, + seedStore); } } // Act PlayerInventoryMutationOutcome denyOutcome; - using (var scope = Factory.Services.CreateScope()) + using (var actScope = Factory.Services.CreateScope()) { - var registry = scope.ServiceProvider.GetRequiredService(); - var store = scope.ServiceProvider.GetRequiredService(); + var actRegistry = actScope.ServiceProvider.GetRequiredService(); + var actStore = actScope.ServiceProvider.GetRequiredService(); denyOutcome = PlayerInventoryOperations.TryAddStack( "dev-local-1", "scrap_metal_bulk", quantity: 1, - registry, - store); + actRegistry, + actStore); } // Act — verify on fresh host @@ -120,27 +120,19 @@ public sealed class PlayerInventoryPersistenceIntegrationTests(PostgresIntegrati var inventoryDdl = await File.ReadAllTextAsync(inventoryDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyInventory = new NpgsqlCommand(inventoryDdl, conn)) - { - await applyInventory.ExecuteNonQueryAsync(); - } + await using var applyInventory = new NpgsqlCommand(inventoryDdl, conn); + await applyInventory.ExecuteNonQueryAsync(); - await using (var truncateInventory = new NpgsqlCommand("TRUNCATE player_inventory;", conn)) - { - await truncateInventory.ExecuteNonQueryAsync(); - } + await using var truncateInventory = new NpgsqlCommand("TRUNCATE player_inventory;", conn); + await truncateInventory.ExecuteNonQueryAsync(); } private static int OccupiedBagSlotCount(PlayerInventorySnapshot snapshot) diff --git a/server/NeonSprawl.Server.Tests/Game/Mastery/MasteryCatalogRegistryTests.cs b/server/NeonSprawl.Server.Tests/Game/Mastery/MasteryCatalogRegistryTests.cs index 44b47df..33330ce 100644 --- a/server/NeonSprawl.Server.Tests/Game/Mastery/MasteryCatalogRegistryTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Mastery/MasteryCatalogRegistryTests.cs @@ -89,7 +89,7 @@ public class MasteryCatalogRegistryTests // Act var tracks = registry.GetTracksInSkillIdOrder(); // Assert - Assert.Equal(["intrusion", "refine", "salvage"], tracks.Select(t => t.SkillId).ToArray()); + Assert.Equal(["intrusion", "refine", "salvage"], [.. tracks.Select(t => t.SkillId)]); } [Fact] @@ -111,7 +111,7 @@ public class MasteryCatalogRegistryTests // Act var ordered = registry.GetPerksInIdOrder(); // Assert - Assert.Equal(["a_perk", "m_perk", "z_perk"], ordered.Select(p => p.Id).ToArray()); + Assert.Equal(["a_perk", "m_perk", "z_perk"], [.. ordered.Select(p => p.Id)]); } [Fact] diff --git a/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStateApiTests.cs b/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStateApiTests.cs index d6372a4..67aeb35 100644 --- a/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStateApiTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStateApiTests.cs @@ -158,10 +158,9 @@ public sealed class PerkStateApiTests // Assert Assert.Equal(HttpStatusCode.OK, post.StatusCode); var json = await post.Content.ReadAsStringAsync(); - using (var doc = JsonDocument.Parse(json)) - { - Assert.False(doc.RootElement.TryGetProperty("reasonCode", out _)); - } + using var doc = JsonDocument.Parse(json); + Assert.False(doc.RootElement.TryGetProperty("reasonCode", out _)); + var envelope = JsonSerializer.Deserialize(json); Assert.NotNull(envelope); diff --git a/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStatePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStatePersistenceIntegrationTests.cs index 776aff6..d06a1dd 100644 --- a/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStatePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Mastery/PerkStatePersistenceIntegrationTests.cs @@ -20,23 +20,20 @@ public sealed class PerkStatePersistenceIntegrationTests(PostgresIntegrationHarn // Arrange await ResetPerkTablesAsync(); const string playerId = "dev-local-1"; - using (var scope = Factory.Services.CreateScope()) - { - var xpStore = scope.ServiceProvider.GetRequiredService(); - var engine = scope.ServiceProvider.GetRequiredService(); - Assert.True(xpStore.TryApplyXpDelta(playerId, "salvage", 450, out _, out _)); - Assert.True(engine.TrySelectBranch(playerId, "salvage", 1, "scrap_efficiency").Success); - _ = engine.ReevaluateAfterLevelUp(playerId, "salvage", newLevel: 4); - } + using var scope = Factory.Services.CreateScope(); + var xpStore = scope.ServiceProvider.GetRequiredService(); + var engine = scope.ServiceProvider.GetRequiredService(); + Assert.True(xpStore.TryApplyXpDelta(playerId, "salvage", 450, out _, out _)); + Assert.True(engine.TrySelectBranch(playerId, "salvage", 1, "scrap_efficiency").Success); + _ = engine.ReevaluateAfterLevelUp(playerId, "salvage", newLevel: 4); + // Act PerkStateSnapshot snapshot; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var scope = secondFactory.Services.CreateScope(); - var perkStore = scope.ServiceProvider.GetRequiredService(); - snapshot = perkStore.GetSnapshot(playerId); - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var perkStore = secondScope.ServiceProvider.GetRequiredService(); + snapshot = perkStore.GetSnapshot(playerId); // Assert Assert.Equal("scrap_efficiency", snapshot.BranchPicksBySkillId["salvage"][1]); @@ -65,21 +62,15 @@ public sealed class PerkStatePersistenceIntegrationTests(PostgresIntegrationHarn var perkDdl = await File.ReadAllTextAsync(perkDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyPerk = new NpgsqlCommand(perkDdl, conn)) - { - await applyPerk.ExecuteNonQueryAsync(); - } + await using var applyPerk = new NpgsqlCommand(perkDdl, conn); + await applyPerk.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresPositionStateIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresPositionStateIntegrationTests.cs index b0a6ef1..e47922c 100644 --- a/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresPositionStateIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresPositionStateIntegrationTests.cs @@ -62,12 +62,11 @@ public sealed class PostgresPositionStateIntegrationTests(PostgresIntegrationHar PositionStateResponse? persisted = null; // Act - using (var first = Factory.CreateClient()) - { - var post = await first.PostAsJsonAsync("/game/players/dev-local-1/move", cmd); - postStatus = post.StatusCode; - persisted = await post.Content.ReadFromJsonAsync(); - } + using var first = Factory.CreateClient(); + var post = await first.PostAsJsonAsync("/game/players/dev-local-1/move", cmd); + postStatus = post.StatusCode; + persisted = await post.Content.ReadFromJsonAsync(); + // Assert await using var secondFactory = new PostgresWebApplicationFactory(); @@ -122,15 +121,11 @@ public sealed class PostgresPositionStateIntegrationTests(PostgresIntegrationHar var ddl = await File.ReadAllTextAsync(ddlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var apply = new NpgsqlCommand(ddl, conn)) - { - await apply.ExecuteNonQueryAsync(); - } + await using var apply = new NpgsqlCommand(ddl, conn); + await apply.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); } diff --git a/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs index 08b3063..60f43fa 100644 --- a/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs @@ -70,17 +70,15 @@ public sealed class PlayerQuestProgressPersistenceIntegrationTests(PostgresInteg // Act — read back through a fresh host QuestStepState readBack; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var store = secondScope.ServiceProvider.GetRequiredService(); - readBack = store.TryGetProgress( - PlayerId, - PrototypeE7M1QuestCatalogRules.GatherIntroQuestId, - out var snapshot) - ? snapshot - : null!; - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readStore = secondScope.ServiceProvider.GetRequiredService(); + readBack = readStore.TryGetProgress( + PlayerId, + PrototypeE7M1QuestCatalogRules.GatherIntroQuestId, + out var snapshot) + ? snapshot + : null!; // Assert Assert.NotNull(readBack); @@ -108,12 +106,10 @@ public sealed class PlayerQuestProgressPersistenceIntegrationTests(PostgresInteg } var readBackExists = false; - await using (var secondFactory = new PostgresWebApplicationFactory()) - { - using var secondScope = secondFactory.Services.CreateScope(); - var store = secondScope.ServiceProvider.GetRequiredService(); - readBackExists = store.TryGetProgress(PlayerId, questId, out _); - } + await using var secondFactory = new PostgresWebApplicationFactory(); + using var secondScope = secondFactory.Services.CreateScope(); + var readStore = secondScope.ServiceProvider.GetRequiredService(); + readBackExists = readStore.TryGetProgress(PlayerId, questId, out _); // Assert Assert.False(readBackExists); @@ -141,21 +137,15 @@ public sealed class PlayerQuestProgressPersistenceIntegrationTests(PostgresInteg var questDdl = await File.ReadAllTextAsync(questDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyQuest = new NpgsqlCommand(questDdl, conn)) - { - await applyQuest.ExecuteNonQueryAsync(); - } + await using var applyQuest = new NpgsqlCommand(questDdl, conn); + await applyQuest.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server.Tests/Game/Quests/QuestDefinitionsWorldApiTests.cs b/server/NeonSprawl.Server.Tests/Game/Quests/QuestDefinitionsWorldApiTests.cs index a50c853..7ea7e59 100644 --- a/server/NeonSprawl.Server.Tests/Game/Quests/QuestDefinitionsWorldApiTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Quests/QuestDefinitionsWorldApiTests.cs @@ -24,8 +24,8 @@ public class QuestDefinitionsWorldApiTests Assert.NotNull(body.Quests); Assert.Equal(PrototypeE7M1QuestCatalogRules.ExpectedQuestIds.Count, body.Quests.Count); Assert.Equal( - PrototypeE7M1QuestCatalogRules.ExpectedQuestIds.Order(StringComparer.Ordinal).ToArray(), - body.Quests.Select(q => q.Id).ToArray()); + [.. PrototypeE7M1QuestCatalogRules.ExpectedQuestIds.Order(StringComparer.Ordinal)], + [.. body.Quests.Select(q => q.Id)]); var gatherIntro = body.Quests.Single(q => q.Id == "prototype_quest_gather_intro"); Assert.Equal("Intro: Salvage Run", gatherIntro.DisplayName); diff --git a/server/NeonSprawl.Server.Tests/Game/Quests/QuestProgressApiTests.cs b/server/NeonSprawl.Server.Tests/Game/Quests/QuestProgressApiTests.cs index ddf974b..258bb65 100644 --- a/server/NeonSprawl.Server.Tests/Game/Quests/QuestProgressApiTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Quests/QuestProgressApiTests.cs @@ -63,7 +63,7 @@ public sealed class QuestProgressApiTests await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var registry = factory.Services.GetRequiredService(); - var expectedQuestOrder = registry.GetDefinitionsInIdOrder().Select(static d => d.Id).ToArray(); + string[] expectedQuestOrder = [.. registry.GetDefinitionsInIdOrder().Select(static d => d.Id)]; // Act var response = await client.GetAsync($"/game/players/{PlayerId}/quest-progress"); @@ -75,7 +75,8 @@ public sealed class QuestProgressApiTests Assert.Equal(QuestProgressListResponse.CurrentSchemaVersion, body!.SchemaVersion); Assert.Equal(PlayerId, body.PlayerId); Assert.Equal(expectedQuestOrder.Length, body.Quests.Count); - Assert.Equal(expectedQuestOrder, body.Quests.Select(static row => row.QuestId).ToArray()); + string[] actualQuestOrder = [.. body.Quests.Select(static row => row.QuestId)]; + Assert.Equal(expectedQuestOrder, actualQuestOrder); foreach (var row in body.Quests) { Assert.Equal(QuestProgressApi.StatusNotStarted, row.Status); diff --git a/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantPersistenceIntegrationTests.cs index adf0d4a..dec654b 100644 --- a/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantPersistenceIntegrationTests.cs @@ -29,12 +29,11 @@ public sealed class SkillProgressionGrantPersistenceIntegrationTests(PostgresInt HttpResponseMessage postResponse; // Act — write grant through first host - using (var firstClient = Factory.CreateClient()) - { - postResponse = await firstClient.PostAsJsonAsync( - "/game/players/dev-local-1/skill-progression", - grant); - } + using var firstClient = Factory.CreateClient(); + postResponse = await firstClient.PostAsJsonAsync( + "/game/players/dev-local-1/skill-progression", + grant); + // Act — read back through a fresh host (forces DB round-trip / new DI scope) await using var secondFactory = new PostgresWebApplicationFactory(); @@ -78,26 +77,18 @@ public sealed class SkillProgressionGrantPersistenceIntegrationTests(PostgresInt var progressionDdl = await File.ReadAllTextAsync(progressionDdlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); - await using (var applyPosition = new NpgsqlCommand(positionDdl, conn)) - { - await applyPosition.ExecuteNonQueryAsync(); - } + await using var applyPosition = new NpgsqlCommand(positionDdl, conn); + await applyPosition.ExecuteNonQueryAsync(); - await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn)) - { - await truncate.ExecuteNonQueryAsync(); - } + await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn); + await truncate.ExecuteNonQueryAsync(); PostgresPositionBootstrap.SeedDevPlayer(conn, options); - await using (var applyProgression = new NpgsqlCommand(progressionDdl, conn)) - { - await applyProgression.ExecuteNonQueryAsync(); - } + await using var applyProgression = new NpgsqlCommand(progressionDdl, conn); + await applyProgression.ExecuteNonQueryAsync(); - await using (var truncateProgression = new NpgsqlCommand("TRUNCATE player_skill_progression;", conn)) - { - await truncateProgression.ExecuteNonQueryAsync(); - } + await using var truncateProgression = new NpgsqlCommand("TRUNCATE player_skill_progression;", conn); + await truncateProgression.ExecuteNonQueryAsync(); } } diff --git a/server/NeonSprawl.Server/Game/AbilityInput/PostgresHotbarLoadoutBootstrap.cs b/server/NeonSprawl.Server/Game/AbilityInput/PostgresHotbarLoadoutBootstrap.cs index 43ce203..d64e0c1 100644 --- a/server/NeonSprawl.Server/Game/AbilityInput/PostgresHotbarLoadoutBootstrap.cs +++ b/server/NeonSprawl.Server/Game/AbilityInput/PostgresHotbarLoadoutBootstrap.cs @@ -1,10 +1,12 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.AbilityInput; /// Applies NEO-29 hotbar loadout table DDL once per process. public static class PostgresHotbarLoadoutBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V002__player_hotbar_loadout.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; public static void EnsureSchema(Npgsql.NpgsqlDataSource dataSource) diff --git a/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionCatalogLoader.cs index 255d8a2..781c5e9 100644 --- a/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionCatalogLoader.cs @@ -26,9 +26,8 @@ public static class AbilityDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(abilitiesDirectory, "*_abilities.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(abilitiesDirectory, "*_abilities.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_abilities.json files under {abilitiesDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionRegistry.cs index c47cbc9..2bafca8 100644 --- a/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Combat/AbilityDefinitionRegistry.cs @@ -43,7 +43,7 @@ public sealed class AbilityDefinitionRegistry(AbilityDefinitionCatalog catalog) private static IReadOnlyList BuildDefinitionsInIdOrder(AbilityDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Contracts/ContractTemplateCatalogLoader.cs b/server/NeonSprawl.Server/Game/Contracts/ContractTemplateCatalogLoader.cs index e04c072..d017909 100644 --- a/server/NeonSprawl.Server/Game/Contracts/ContractTemplateCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Contracts/ContractTemplateCatalogLoader.cs @@ -62,9 +62,8 @@ public static class ContractTemplateCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(contractsDirectory, "*_contract_templates.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(contractsDirectory, "*_contract_templates.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_contract_templates.json files under {contractsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Contracts/ContractTemplateRegistry.cs b/server/NeonSprawl.Server/Game/Contracts/ContractTemplateRegistry.cs index b9056f7..b64b7ac 100644 --- a/server/NeonSprawl.Server/Game/Contracts/ContractTemplateRegistry.cs +++ b/server/NeonSprawl.Server/Game/Contracts/ContractTemplateRegistry.cs @@ -27,7 +27,7 @@ public sealed class ContractTemplateRegistry(ContractTemplateCatalog catalog) : /// public IReadOnlyList GetDefinitionsInIdOrder() { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) list.Add(catalog.ById[id]); diff --git a/server/NeonSprawl.Server/Game/Contracts/InMemoryContractOutcomeStore.cs b/server/NeonSprawl.Server/Game/Contracts/InMemoryContractOutcomeStore.cs index 842e2f1..0e502df 100644 --- a/server/NeonSprawl.Server/Game/Contracts/InMemoryContractOutcomeStore.cs +++ b/server/NeonSprawl.Server/Game/Contracts/InMemoryContractOutcomeStore.cs @@ -73,7 +73,7 @@ public sealed class InMemoryContractOutcomeStore : IContractOutcomeStore query = query.Take(limit.Value); } - return query.ToArray(); + return [..query]; } /// @@ -85,10 +85,9 @@ public sealed class InMemoryContractOutcomeStore : IContractOutcomeStore return false; } - var idsToRemove = rowsById.Values + string[] idsToRemove = [.. rowsById.Values .Where(row => string.Equals(row.ContractInstanceId, instanceId, StringComparison.Ordinal)) - .Select(static row => row.Id) - .ToArray(); + .Select(static row => row.Id)]; foreach (var id in idsToRemove) { @@ -124,9 +123,9 @@ public sealed class InMemoryContractOutcomeStore : IContractOutcomeStore PlayerId = playerId, ContractInstanceId = instanceId, IdempotencyKey = idempotencyKey, - GrantedItems = row.GrantedItems.ToArray(), - GrantedSkillXp = row.GrantedSkillXp.ToArray(), - GrantedReputation = row.GrantedReputation.ToArray(), + GrantedItems = [..row.GrantedItems], + GrantedSkillXp = [..row.GrantedSkillXp], + GrantedReputation = [..row.GrantedReputation], }; return true; } diff --git a/server/NeonSprawl.Server/Game/Contracts/PostgresContractInstanceBootstrap.cs b/server/NeonSprawl.Server/Game/Contracts/PostgresContractInstanceBootstrap.cs index c2e7de6..87fedd1 100644 --- a/server/NeonSprawl.Server/Game/Contracts/PostgresContractInstanceBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Contracts/PostgresContractInstanceBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Contracts; /// Applies NEO-146 contract instance table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresContractInstanceBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V011__contract_instance.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeBootstrap.cs b/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeBootstrap.cs index a85e846..3e56365 100644 --- a/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Contracts; /// Applies NEO-146 contract outcome table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresContractOutcomeBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V012__contract_outcome.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeStore.cs b/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeStore.cs index 4089fff..fcd694d 100644 --- a/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeStore.cs +++ b/server/NeonSprawl.Server/Game/Contracts/PostgresContractOutcomeStore.cs @@ -162,19 +162,19 @@ public sealed class PostgresContractOutcomeStore(Npgsql.NpgsqlDataSource dataSou private static string SerializeGrants(IReadOnlyList grants) => JsonSerializer.Serialize(grants, JsonOptions); - private static IReadOnlyList DeserializeItems(string json) + private static RewardItemGrantApplied[] DeserializeItems(string json) { var parsed = JsonSerializer.Deserialize(json, JsonOptions); return parsed ?? []; } - private static IReadOnlyList DeserializeSkillXp(string json) + private static RewardSkillXpGrantApplied[] DeserializeSkillXp(string json) { var parsed = JsonSerializer.Deserialize(json, JsonOptions); return parsed ?? []; } - private static IReadOnlyList DeserializeReputation(string json) + private static RewardReputationGrantApplied[] DeserializeReputation(string json) { var parsed = JsonSerializer.Deserialize(json, JsonOptions); return parsed ?? []; @@ -201,9 +201,9 @@ public sealed class PostgresContractOutcomeStore(Npgsql.NpgsqlDataSource dataSou PlayerId = playerId, ContractInstanceId = instanceId, IdempotencyKey = idempotencyKey, - GrantedItems = row.GrantedItems.ToArray(), - GrantedSkillXp = row.GrantedSkillXp.ToArray(), - GrantedReputation = row.GrantedReputation.ToArray(), + GrantedItems = [..row.GrantedItems], + GrantedSkillXp = [..row.GrantedSkillXp], + GrantedReputation = [..row.GrantedReputation], }; return true; } diff --git a/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionCatalogLoader.cs index f43a8f4..0800c67 100644 --- a/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionCatalogLoader.cs @@ -36,9 +36,8 @@ public static class RecipeDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(recipesDirectory, "*_recipes.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(recipesDirectory, "*_recipes.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_recipes.json files under {recipesDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionRegistry.cs index 7b84018..206a400 100644 --- a/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionRegistry.cs @@ -24,7 +24,7 @@ public sealed class RecipeDefinitionRegistry(RecipeDefinitionCatalog catalog) : private static IReadOnlyList BuildDefinitionsInIdOrder(RecipeDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionCatalogLoader.cs index 4acec77..32137f4 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionCatalogLoader.cs @@ -30,9 +30,8 @@ public static class EncounterDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(encountersDirectory, "*_encounters.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(encountersDirectory, "*_encounters.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_encounters.json files under {encountersDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionRegistry.cs index ba911a2..5e04fb3 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterDefinitionRegistry.cs @@ -43,7 +43,7 @@ public sealed class EncounterDefinitionRegistry(EncounterDefinitionCatalog catal private static IReadOnlyList BuildDefinitionsInIdOrder(EncounterDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Encounters/InMemoryEncounterCompleteEventStore.cs b/server/NeonSprawl.Server/Game/Encounters/InMemoryEncounterCompleteEventStore.cs index 539b549..0f036d8 100644 --- a/server/NeonSprawl.Server/Game/Encounters/InMemoryEncounterCompleteEventStore.cs +++ b/server/NeonSprawl.Server/Game/Encounters/InMemoryEncounterCompleteEventStore.cs @@ -27,7 +27,7 @@ public sealed class InMemoryEncounterCompleteEventStore : IEncounterCompleteEven eventsByKey[key] = completeEvent with { - GrantedItems = completeEvent.GrantedItems.ToArray(), + GrantedItems = [..completeEvent.GrantedItems], }; return true; } diff --git a/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionCatalogLoader.cs index b510ede..9a71202 100644 --- a/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionCatalogLoader.cs @@ -35,9 +35,8 @@ public static class RewardTableDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(rewardTablesDirectory, "*_reward_tables.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(rewardTablesDirectory, "*_reward_tables.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_reward_tables.json files under {rewardTablesDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionRegistry.cs index b5d61dc..a7da70b 100644 --- a/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Encounters/RewardTableDefinitionRegistry.cs @@ -43,7 +43,7 @@ public sealed class RewardTableDefinitionRegistry(RewardTableDefinitionCatalog c private static IReadOnlyList BuildDefinitionsInIdOrder(RewardTableDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Factions/FactionDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Factions/FactionDefinitionCatalogLoader.cs index 68fdbc7..6497c6e 100644 --- a/server/NeonSprawl.Server/Game/Factions/FactionDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Factions/FactionDefinitionCatalogLoader.cs @@ -26,9 +26,8 @@ public static class FactionDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(factionsDirectory, "*_factions.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(factionsDirectory, "*_factions.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_factions.json files under {factionsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Factions/FactionDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Factions/FactionDefinitionRegistry.cs index 277adcf..63a59e4 100644 --- a/server/NeonSprawl.Server/Game/Factions/FactionDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Factions/FactionDefinitionRegistry.cs @@ -27,7 +27,7 @@ public sealed class FactionDefinitionRegistry(FactionDefinitionCatalog catalog) /// public IReadOnlyList GetDefinitionsInIdOrder() { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Factions/InMemoryReputationDeltaStore.cs b/server/NeonSprawl.Server/Game/Factions/InMemoryReputationDeltaStore.cs index bc226c1..a33f2e4 100644 --- a/server/NeonSprawl.Server/Game/Factions/InMemoryReputationDeltaStore.cs +++ b/server/NeonSprawl.Server/Game/Factions/InMemoryReputationDeltaStore.cs @@ -58,7 +58,7 @@ public sealed class InMemoryReputationDeltaStore : IReputationDeltaStore query = query.Take(limit.Value); } - return query.ToArray(); + return [..query]; } /// @@ -71,13 +71,12 @@ public sealed class InMemoryReputationDeltaStore : IReputationDeltaStore return false; } - var idsToRemove = rowsById.Values + string[] idsToRemove = [.. rowsById.Values .Where(row => string.Equals(row.PlayerId, player, StringComparison.Ordinal) && string.Equals(row.SourceKind, ReputationDeltaSourceKinds.QuestCompletion, StringComparison.Ordinal) && string.Equals(row.SourceId, sourceId, StringComparison.Ordinal)) - .Select(static row => row.Id) - .ToArray(); + .Select(static row => row.Id)]; foreach (var id in idsToRemove) { diff --git a/server/NeonSprawl.Server/Game/Factions/PostgresPlayerFactionStandingBootstrap.cs b/server/NeonSprawl.Server/Game/Factions/PostgresPlayerFactionStandingBootstrap.cs index 72414ce..cddf10c 100644 --- a/server/NeonSprawl.Server/Game/Factions/PostgresPlayerFactionStandingBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Factions/PostgresPlayerFactionStandingBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Factions; /// Applies NEO-135 faction standing table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresPlayerFactionStandingBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V009__player_faction_standing.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Factions/PostgresReputationDeltaBootstrap.cs b/server/NeonSprawl.Server/Game/Factions/PostgresReputationDeltaBootstrap.cs index 30439e7..92f4b57 100644 --- a/server/NeonSprawl.Server/Game/Factions/PostgresReputationDeltaBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Factions/PostgresReputationDeltaBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Factions; /// Applies NEO-135 reputation delta audit table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresReputationDeltaBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V010__reputation_delta_audit.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Gathering/PostgresResourceNodeInstanceBootstrap.cs b/server/NeonSprawl.Server/Game/Gathering/PostgresResourceNodeInstanceBootstrap.cs index 8ba1705..f1fbca1 100644 --- a/server/NeonSprawl.Server/Game/Gathering/PostgresResourceNodeInstanceBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Gathering/PostgresResourceNodeInstanceBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Gathering; /// Applies NEO-61 resource-node instance table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresResourceNodeInstanceBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V006__resource_node_instance.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalogLoader.cs b/server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalogLoader.cs index 1563904..7945861 100644 --- a/server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalogLoader.cs @@ -35,15 +35,13 @@ public static class ResourceNodeCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var nodeJsonFiles = Directory.GetFiles(resourceNodesDirectory, "*_resource_nodes.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] nodeJsonFiles = [.. Directory.GetFiles(resourceNodesDirectory, "*_resource_nodes.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (nodeJsonFiles.Length == 0) errors.Add($"error: no *_resource_nodes.json files under {resourceNodesDirectory}"); - var yieldJsonFiles = Directory.GetFiles(resourceNodesDirectory, "*_resource_yields.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] yieldJsonFiles = [.. Directory.GetFiles(resourceNodesDirectory, "*_resource_yields.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (yieldJsonFiles.Length == 0) errors.Add($"error: no *_resource_yields.json files under {resourceNodesDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Gigs/PostgresGigProgressionBootstrap.cs b/server/NeonSprawl.Server/Game/Gigs/PostgresGigProgressionBootstrap.cs index 905f6f4..5341f37 100644 --- a/server/NeonSprawl.Server/Game/Gigs/PostgresGigProgressionBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Gigs/PostgresGigProgressionBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Gigs; /// Applies NEO-44 gig progression table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresGigProgressionBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V007__player_gig_progression.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalogLoader.cs index 2edce09..78c85d8 100644 --- a/server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalogLoader.cs @@ -26,9 +26,8 @@ public static class ItemDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(itemsDirectory, "*_items.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(itemsDirectory, "*_items.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_items.json files under {itemsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Items/ItemDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Items/ItemDefinitionRegistry.cs index c6af36a..9b236f3 100644 --- a/server/NeonSprawl.Server/Game/Items/ItemDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Items/ItemDefinitionRegistry.cs @@ -27,7 +27,7 @@ public sealed class ItemDefinitionRegistry(ItemDefinitionCatalog catalog) : IIte /// public IReadOnlyList GetDefinitionsInIdOrder() { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Items/PostgresPlayerInventoryBootstrap.cs b/server/NeonSprawl.Server/Game/Items/PostgresPlayerInventoryBootstrap.cs index 9286068..1ffa86c 100644 --- a/server/NeonSprawl.Server/Game/Items/PostgresPlayerInventoryBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Items/PostgresPlayerInventoryBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Items; /// Applies NEO-54 inventory table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresPlayerInventoryBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V005__player_inventory.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Mastery/MasteryCatalogLoader.cs b/server/NeonSprawl.Server/Game/Mastery/MasteryCatalogLoader.cs index 1afdf7d..63f53d8 100644 --- a/server/NeonSprawl.Server/Game/Mastery/MasteryCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Mastery/MasteryCatalogLoader.cs @@ -29,9 +29,8 @@ public static class MasteryCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(masteryDirectory, "*_mastery.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(masteryDirectory, "*_mastery.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_mastery.json files under {masteryDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Mastery/PostgresPerkStateBootstrap.cs b/server/NeonSprawl.Server/Game/Mastery/PostgresPerkStateBootstrap.cs index 1d8ebfd..aea17f2 100644 --- a/server/NeonSprawl.Server/Game/Mastery/PostgresPerkStateBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Mastery/PostgresPerkStateBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Mastery; /// Applies NEO-47 perk state table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresPerkStateBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V004__player_perk_state.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalogLoader.cs index 6c16516..4838e98 100644 --- a/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalogLoader.cs @@ -26,9 +26,8 @@ public static class NpcBehaviorDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(npcBehaviorsDirectory, "*_npc_behaviors.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(npcBehaviorsDirectory, "*_npc_behaviors.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_npc_behaviors.json files under {npcBehaviorsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionRegistry.cs index 2ae0b96..9eb6581 100644 --- a/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionRegistry.cs @@ -43,7 +43,7 @@ public sealed class NpcBehaviorDefinitionRegistry(NpcBehaviorDefinitionCatalog c private static IReadOnlyList BuildDefinitionsInIdOrder(NpcBehaviorDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/PositionState/PostgresPositionBootstrap.cs b/server/NeonSprawl.Server/Game/PositionState/PostgresPositionBootstrap.cs index 090c57a..47cbf47 100644 --- a/server/NeonSprawl.Server/Game/PositionState/PostgresPositionBootstrap.cs +++ b/server/NeonSprawl.Server/Game/PositionState/PostgresPositionBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.PositionState; /// @@ -8,7 +10,7 @@ public static class PostgresPositionBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V001__player_position.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; /// @@ -36,10 +38,9 @@ public static class PostgresPositionBootstrap var ddl = File.ReadAllText(ddlPath); using var conn = dataSource.OpenConnection(); - using (var cmd = new Npgsql.NpgsqlCommand(ddl, conn)) - { - cmd.ExecuteNonQuery(); - } + using var cmd = new Npgsql.NpgsqlCommand(ddl, conn); + cmd.ExecuteNonQuery(); + System.Threading.Volatile.Write(ref _schemaReady, 1); } diff --git a/server/NeonSprawl.Server/Game/Quests/PostgresPlayerQuestProgressBootstrap.cs b/server/NeonSprawl.Server/Game/Quests/PostgresPlayerQuestProgressBootstrap.cs index 4d528db..db1cec5 100644 --- a/server/NeonSprawl.Server/Game/Quests/PostgresPlayerQuestProgressBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Quests/PostgresPlayerQuestProgressBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Quests; /// Applies NEO-116 quest progress table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresPlayerQuestProgressBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V008__player_quest_progress.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M2QuestCatalogRules.cs b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M2QuestCatalogRules.cs index 321638f..d98e531 100644 --- a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M2QuestCatalogRules.cs +++ b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M2QuestCatalogRules.cs @@ -126,14 +126,12 @@ public static class PrototypeE7M2QuestCatalogRules private static NormalizedBundle NormalizeBundle(QuestRewardBundleRow bundle) => new( - bundle.ItemGrants + [.. bundle.ItemGrants .OrderBy(g => g.ItemId, StringComparer.Ordinal) - .ThenBy(g => g.Quantity) - .ToArray(), - bundle.SkillXpGrants + .ThenBy(g => g.Quantity)], + [.. bundle.SkillXpGrants .OrderBy(g => g.SkillId, StringComparer.Ordinal) - .ThenBy(g => g.Amount) - .ToArray()); + .ThenBy(g => g.Amount)]); private static bool BundlesEqual(NormalizedBundle left, NormalizedBundle right) { diff --git a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs index bc925b4..c30840b 100644 --- a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs +++ b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs @@ -161,18 +161,15 @@ public static class PrototypeE7M3QuestFactionRules private static NormalizedBundle NormalizeBundle(QuestRewardBundleRow bundle) => new( - bundle.ItemGrants + [.. bundle.ItemGrants .OrderBy(g => g.ItemId, StringComparer.Ordinal) - .ThenBy(g => g.Quantity) - .ToArray(), - bundle.SkillXpGrants + .ThenBy(g => g.Quantity)], + [.. bundle.SkillXpGrants .OrderBy(g => g.SkillId, StringComparer.Ordinal) - .ThenBy(g => g.Amount) - .ToArray(), - bundle.ReputationGrants + .ThenBy(g => g.Amount)], + [.. bundle.ReputationGrants .OrderBy(g => g.FactionId, StringComparer.Ordinal) - .ThenBy(g => g.Amount) - .ToArray()); + .ThenBy(g => g.Amount)]); private static bool BundlesEqual(NormalizedBundle left, NormalizedBundle right) { diff --git a/server/NeonSprawl.Server/Game/Quests/QuestDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Quests/QuestDefinitionCatalogLoader.cs index 77e23c5..5d36b66 100644 --- a/server/NeonSprawl.Server/Game/Quests/QuestDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Quests/QuestDefinitionCatalogLoader.cs @@ -64,9 +64,8 @@ public static class QuestDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(questsDirectory, "*_quests.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(questsDirectory, "*_quests.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_quests.json files under {questsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Quests/QuestDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Quests/QuestDefinitionRegistry.cs index b6c2d3b..6c97d05 100644 --- a/server/NeonSprawl.Server/Game/Quests/QuestDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Quests/QuestDefinitionRegistry.cs @@ -43,7 +43,7 @@ public sealed class QuestDefinitionRegistry(QuestDefinitionCatalog catalog) : IQ private static IReadOnlyList BuildDefinitionsInIdOrder(QuestDefinitionCatalog catalog) { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) { diff --git a/server/NeonSprawl.Server/Game/Rewards/InMemoryRewardDeliveryStore.cs b/server/NeonSprawl.Server/Game/Rewards/InMemoryRewardDeliveryStore.cs index b9a89ed..2ecc621 100644 --- a/server/NeonSprawl.Server/Game/Rewards/InMemoryRewardDeliveryStore.cs +++ b/server/NeonSprawl.Server/Game/Rewards/InMemoryRewardDeliveryStore.cs @@ -27,9 +27,9 @@ public sealed class InMemoryRewardDeliveryStore : IRewardDeliveryStore eventsByKey[key] = deliveryEvent with { - GrantedItems = deliveryEvent.GrantedItems.ToArray(), - GrantedSkillXp = deliveryEvent.GrantedSkillXp.ToArray(), - GrantedReputation = deliveryEvent.GrantedReputation.ToArray(), + GrantedItems = [..deliveryEvent.GrantedItems], + GrantedSkillXp = [..deliveryEvent.GrantedSkillXp], + GrantedReputation = [..deliveryEvent.GrantedReputation], }; return true; } diff --git a/server/NeonSprawl.Server/Game/Skills/PostgresSkillProgressionBootstrap.cs b/server/NeonSprawl.Server/Game/Skills/PostgresSkillProgressionBootstrap.cs index 2eb3844..3753a37 100644 --- a/server/NeonSprawl.Server/Game/Skills/PostgresSkillProgressionBootstrap.cs +++ b/server/NeonSprawl.Server/Game/Skills/PostgresSkillProgressionBootstrap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace NeonSprawl.Server.Game.Skills; /// Applies NEO-38 skill progression table DDL once per process. @@ -5,7 +7,7 @@ public static class PostgresSkillProgressionBootstrap { private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V003__player_skill_progression.sql"); - private static readonly object SchemaGate = new(); + private static readonly Lock SchemaGate = new(); private static int _schemaReady; diff --git a/server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs b/server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs index 4e9a35f..0e655f6 100644 --- a/server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs +++ b/server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs @@ -26,9 +26,8 @@ public static class SkillDefinitionCatalogLoader if (errors.Count > 0) ThrowIfAny(errors); - var jsonFiles = Directory.GetFiles(skillsDirectory, "*_skills.json", SearchOption.TopDirectoryOnly) - .OrderBy(p => p, StringComparer.Ordinal) - .ToArray(); + string[] jsonFiles = [.. Directory.GetFiles(skillsDirectory, "*_skills.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.Ordinal)]; if (jsonFiles.Length == 0) errors.Add($"error: no *_skills.json files under {skillsDirectory}"); diff --git a/server/NeonSprawl.Server/Game/Skills/SkillDefinitionRegistry.cs b/server/NeonSprawl.Server/Game/Skills/SkillDefinitionRegistry.cs index fca38d2..f9cf20f 100644 --- a/server/NeonSprawl.Server/Game/Skills/SkillDefinitionRegistry.cs +++ b/server/NeonSprawl.Server/Game/Skills/SkillDefinitionRegistry.cs @@ -27,7 +27,7 @@ public sealed class SkillDefinitionRegistry(SkillDefinitionCatalog catalog) : IS /// public IReadOnlyList GetDefinitionsInIdOrder() { - var ids = catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray(); + string[] ids = [.. catalog.ById.Keys.OrderBy(k => k, StringComparer.Ordinal)]; var list = new List(ids.Length); foreach (var id in ids) {