using NeonSprawl.Server.Game.Items; using NeonSprawl.Server.Game.Mastery; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Skills; namespace NeonSprawl.Server.Game.Crafting; /// Maps POST /game/players/{{id}}/craft — craft execution (NEO-70). public static class PlayerCraftApi { public static WebApplication MapPlayerCraftApi(this WebApplication app) { app.MapPost( "/game/players/{id}/craft", (string id, CraftRequest? body, IPositionStateStore positions, IRecipeDefinitionRegistry recipeRegistry, IItemDefinitionRegistry itemRegistry, IPlayerInventoryStore inventoryStore, ISkillDefinitionRegistry skillRegistry, IPlayerSkillProgressionStore xpStore, ISkillLevelCurve levelCurve, PerkUnlockEngine perkUnlockEngine) => { if (body is null || body.SchemaVersion != CraftRequest.CurrentSchemaVersion) { return Results.BadRequest(); } if (body.RecipeId is null || body.RecipeId.Trim().Length == 0) { return Results.BadRequest(); } var trimmedId = id.Trim(); if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _)) { return Results.NotFound(); } var quantity = body.Quantity ?? 1; var result = CraftOperations.TryCraft( trimmedId, body.RecipeId, quantity, recipeRegistry, itemRegistry, inventoryStore, skillRegistry, xpStore, levelCurve, perkUnlockEngine); if (!result.Success && (string.Equals(result.ReasonCode, CraftReasonCodes.InventoryStoreMissing, StringComparison.Ordinal) || string.Equals(result.ReasonCode, CraftReasonCodes.ProgressionStoreMissing, StringComparison.Ordinal))) { return Results.NotFound(); } return Results.Json(MapResponse(result)); }); return app; } internal static CraftResponse MapResponse(CraftResult result) { CraftXpGrantSummaryJson? xp = null; if (result.XpGrantSummary is { } summary) { xp = new CraftXpGrantSummaryJson { SkillId = summary.SkillId, Amount = summary.Amount, SourceKind = summary.SourceKind, }; } return new CraftResponse { Success = result.Success, ReasonCode = result.ReasonCode, InputsConsumed = MapIoRows(result.InputsConsumed), OutputsGranted = MapIoRows(result.OutputsGranted), XpGrantSummary = xp, }; } private static IReadOnlyList MapIoRows(IReadOnlyList rows) { if (rows.Count == 0) { return Array.Empty(); } var mapped = new CraftIoAppliedJson[rows.Count]; for (var i = 0; i < rows.Count; i++) { var row = rows[i]; mapped[i] = new CraftIoAppliedJson { ItemId = row.ItemId, Quantity = row.Quantity, }; } return mapped; } }