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.pull/187/head
parent
1922900477
commit
b455ff5ad5
|
|
@ -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<IExampleStore>();
|
||||
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<IExampleStore>();
|
||||
Assert.True(store.TryAppend(row));
|
||||
// … later …
|
||||
var store = secondScope.ServiceProvider.GetRequiredService<IExampleStore>(); // 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<T>` 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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IContractInstanceStore>();
|
||||
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<IContractInstanceStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IContractOutcomeStore>();
|
||||
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<IContractOutcomeStore>();
|
||||
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<IContractOutcomeStore>();
|
||||
appended = outcomeStore.TryAppend(outcomeRow);
|
||||
}
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var outcomeStore = scope.ServiceProvider.GetRequiredService<IContractOutcomeStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IFactionStandingStore>();
|
||||
readBack = store.TryGetStanding(PlayerId, GridFactionId);
|
||||
}
|
||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||
using var secondScope = secondFactory.Services.CreateScope();
|
||||
var readStore = secondScope.ServiceProvider.GetRequiredService<IFactionStandingStore>();
|
||||
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<IFactionStandingStore>();
|
||||
readBack = store.TryGetStanding(PlayerId, GridFactionId);
|
||||
}
|
||||
using var scope = Factory.Services.CreateScope();
|
||||
var store = scope.ServiceProvider.GetRequiredService<IFactionStandingStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -40,12 +40,10 @@ public sealed class ReputationDeltaPersistenceIntegrationTests(PostgresIntegrati
|
|||
}
|
||||
|
||||
IReadOnlyList<ReputationDeltaRow> readBack;
|
||||
await using (var secondFactory = new PostgresWebApplicationFactory())
|
||||
{
|
||||
using var secondScope = secondFactory.Services.CreateScope();
|
||||
var store = secondScope.ServiceProvider.GetRequiredService<IReputationDeltaStore>();
|
||||
readBack = store.GetDeltasForPlayer(PlayerId);
|
||||
}
|
||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||
using var secondScope = secondFactory.Services.CreateScope();
|
||||
var readStore = secondScope.ServiceProvider.GetRequiredService<IReputationDeltaStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresInte
|
|||
await ResetInstanceTableAsync();
|
||||
using (var seedScope = Factory.Services.CreateScope())
|
||||
{
|
||||
var registry = seedScope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
|
||||
var store = seedScope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
|
||||
var seedRegistry = seedScope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
|
||||
var seedStore = seedScope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
|
||||
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<IResourceNodeDefinitionRegistry>();
|
||||
var store = scope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
|
||||
denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store);
|
||||
store.TryGetRemainingGathers(AlphaId, out persisted);
|
||||
}
|
||||
using var actScope = secondFactory.Services.CreateScope();
|
||||
var actRegistry = actScope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
|
||||
var actStore = actScope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,31 +54,31 @@ public sealed class PlayerInventoryPersistenceIntegrationTests(PostgresIntegrati
|
|||
await ResetInventoryTableAsync();
|
||||
using (var seedScope = Factory.Services.CreateScope())
|
||||
{
|
||||
var registry = seedScope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
|
||||
var store = seedScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
|
||||
var seedRegistry = seedScope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
|
||||
var seedStore = seedScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
|
||||
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<IItemDefinitionRegistry>();
|
||||
var store = scope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
|
||||
var actRegistry = actScope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
|
||||
var actStore = actScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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<PerkBranchSelectResponse>(json);
|
||||
Assert.NotNull(envelope);
|
||||
|
|
|
|||
|
|
@ -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<IPlayerSkillProgressionStore>();
|
||||
var engine = scope.ServiceProvider.GetRequiredService<PerkUnlockEngine>();
|
||||
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<IPlayerSkillProgressionStore>();
|
||||
var engine = scope.ServiceProvider.GetRequiredService<PerkUnlockEngine>();
|
||||
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<IPlayerPerkStateStore>();
|
||||
snapshot = perkStore.GetSnapshot(playerId);
|
||||
}
|
||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||
using var secondScope = secondFactory.Services.CreateScope();
|
||||
var perkStore = secondScope.ServiceProvider.GetRequiredService<IPlayerPerkStateStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PositionStateResponse>();
|
||||
}
|
||||
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<PositionStateResponse>();
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IPlayerQuestStateStore>();
|
||||
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<IPlayerQuestStateStore>();
|
||||
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<IPlayerQuestStateStore>();
|
||||
readBackExists = store.TryGetProgress(PlayerId, questId, out _);
|
||||
}
|
||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||
using var secondScope = secondFactory.Services.CreateScope();
|
||||
var readStore = secondScope.ServiceProvider.GetRequiredService<IPlayerQuestStateStore>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public sealed class QuestProgressApiTests
|
|||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var registry = factory.Services.GetRequiredService<IQuestDefinitionRegistry>();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.AbilityInput;
|
||||
|
||||
/// <summary>Applies NEO-29 hotbar loadout table DDL once per process.</summary>
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public sealed class AbilityDefinitionRegistry(AbilityDefinitionCatalog catalog)
|
|||
|
||||
private static IReadOnlyList<AbilityDefRow> 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<AbilityDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class ContractTemplateRegistry(ContractTemplateCatalog catalog) :
|
|||
/// <inheritdoc />
|
||||
public IReadOnlyList<ContractTemplateRow> 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<ContractTemplateRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
list.Add(catalog.ById[id]);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public sealed class InMemoryContractOutcomeStore : IContractOutcomeStore
|
|||
query = query.Take(limit.Value);
|
||||
}
|
||||
|
||||
return query.ToArray();
|
||||
return [..query];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Contracts;
|
||||
|
||||
/// <summary>Applies NEO-146 contract instance table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Contracts;
|
||||
|
||||
/// <summary>Applies NEO-146 contract outcome table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -162,19 +162,19 @@ public sealed class PostgresContractOutcomeStore(Npgsql.NpgsqlDataSource dataSou
|
|||
private static string SerializeGrants<T>(IReadOnlyList<T> grants) =>
|
||||
JsonSerializer.Serialize(grants, JsonOptions);
|
||||
|
||||
private static IReadOnlyList<RewardItemGrantApplied> DeserializeItems(string json)
|
||||
private static RewardItemGrantApplied[] DeserializeItems(string json)
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<RewardItemGrantApplied[]>(json, JsonOptions);
|
||||
return parsed ?? [];
|
||||
}
|
||||
|
||||
private static IReadOnlyList<RewardSkillXpGrantApplied> DeserializeSkillXp(string json)
|
||||
private static RewardSkillXpGrantApplied[] DeserializeSkillXp(string json)
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<RewardSkillXpGrantApplied[]>(json, JsonOptions);
|
||||
return parsed ?? [];
|
||||
}
|
||||
|
||||
private static IReadOnlyList<RewardReputationGrantApplied> DeserializeReputation(string json)
|
||||
private static RewardReputationGrantApplied[] DeserializeReputation(string json)
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize<RewardReputationGrantApplied[]>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public sealed class RecipeDefinitionRegistry(RecipeDefinitionCatalog catalog) :
|
|||
|
||||
private static IReadOnlyList<RecipeDefRow> 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<RecipeDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public sealed class EncounterDefinitionRegistry(EncounterDefinitionCatalog catal
|
|||
|
||||
private static IReadOnlyList<EncounterDefRow> 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<EncounterDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class InMemoryEncounterCompleteEventStore : IEncounterCompleteEven
|
|||
|
||||
eventsByKey[key] = completeEvent with
|
||||
{
|
||||
GrantedItems = completeEvent.GrantedItems.ToArray(),
|
||||
GrantedItems = [..completeEvent.GrantedItems],
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public sealed class RewardTableDefinitionRegistry(RewardTableDefinitionCatalog c
|
|||
|
||||
private static IReadOnlyList<RewardTableDefRow> 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<RewardTableDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class FactionDefinitionRegistry(FactionDefinitionCatalog catalog)
|
|||
/// <inheritdoc />
|
||||
public IReadOnlyList<FactionDefRow> 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<FactionDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public sealed class InMemoryReputationDeltaStore : IReputationDeltaStore
|
|||
query = query.Take(limit.Value);
|
||||
}
|
||||
|
||||
return query.ToArray();
|
||||
return [..query];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Factions;
|
||||
|
||||
/// <summary>Applies NEO-135 faction standing table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Factions;
|
||||
|
||||
/// <summary>Applies NEO-135 reputation delta audit table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Gathering;
|
||||
|
||||
/// <summary>Applies NEO-61 resource-node instance table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Gigs;
|
||||
|
||||
/// <summary>Applies NEO-44 gig progression table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class ItemDefinitionRegistry(ItemDefinitionCatalog catalog) : IIte
|
|||
/// <inheritdoc />
|
||||
public IReadOnlyList<ItemDefRow> 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<ItemDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Items;
|
||||
|
||||
/// <summary>Applies NEO-54 inventory table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Mastery;
|
||||
|
||||
/// <summary>Applies NEO-47 perk state table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public sealed class NpcBehaviorDefinitionRegistry(NpcBehaviorDefinitionCatalog c
|
|||
|
||||
private static IReadOnlyList<NpcBehaviorDefRow> 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<NpcBehaviorDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.PositionState;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Quests;
|
||||
|
||||
/// <summary>Applies NEO-116 quest progress table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public sealed class QuestDefinitionRegistry(QuestDefinitionCatalog catalog) : IQ
|
|||
|
||||
private static IReadOnlyList<QuestDefRow> 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<QuestDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading;
|
||||
|
||||
namespace NeonSprawl.Server.Game.Skills;
|
||||
|
||||
/// <summary>Applies NEO-38 skill progression table DDL once per process.</summary>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public sealed class SkillDefinitionRegistry(SkillDefinitionCatalog catalog) : IS
|
|||
/// <inheritdoc />
|
||||
public IReadOnlyList<SkillDefRow> 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<SkillDefRow>(ids.Length);
|
||||
foreach (var id in ids)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue