using NeonSprawl.Server.Game.Items; using NeonSprawl.Server.Game.Mastery; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Rewards; using NeonSprawl.Server.Game.Skills; namespace NeonSprawl.Server.Game.Quests; /// Maps GET /game/players/{{id}}/quest-progress (NEO-119). public static class QuestProgressApi { public const string StatusNotStarted = "not_started"; public const string StatusActive = "active"; public const string StatusCompleted = "completed"; public static WebApplication MapQuestProgressApi(this WebApplication app) { app.MapGet( "/game/players/{id}/quest-progress", (string id, IPositionStateStore positions, IQuestDefinitionRegistry questRegistry, IPlayerQuestStateStore progressStore, IPlayerInventoryStore inventoryStore, IItemDefinitionRegistry itemRegistry, ISkillDefinitionRegistry skillRegistry, IPlayerSkillProgressionStore skillProgressionStore, ISkillLevelCurve levelCurve, PerkUnlockEngine perkUnlockEngine, IPlayerPerkStateStore perkStore, IRewardDeliveryStore deliveryStore, TimeProvider timeProvider, ILogger? logger) => { var trimmedId = id.Trim(); if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _)) { return Results.NotFound(); } QuestObjectiveWiring.TryRefreshInventoryProgressForPlayer( trimmedId, questRegistry, progressStore, inventoryStore, itemRegistry, skillRegistry, skillProgressionStore, levelCurve, perkUnlockEngine, perkStore, deliveryStore, timeProvider, logger); return Results.Json(BuildSnapshot(trimmedId, questRegistry, progressStore, deliveryStore)); }); return app; } internal static QuestProgressListResponse BuildSnapshot( string playerId, IQuestDefinitionRegistry questRegistry, IPlayerQuestStateStore progressStore, IRewardDeliveryStore deliveryStore) { var defs = questRegistry.GetDefinitionsInIdOrder(); var rows = new List(defs.Count); foreach (var def in defs) { rows.Add(MapQuestProgressRow(playerId, def.Id, progressStore, deliveryStore)); } return new QuestProgressListResponse { PlayerId = playerId, Quests = rows, SchemaVersion = QuestProgressListResponse.CurrentSchemaVersion, }; } internal static QuestProgressRowJson MapQuestProgressRow( string playerId, string questId, IPlayerQuestStateStore progressStore, IRewardDeliveryStore deliveryStore) { if (!progressStore.TryGetProgress(playerId, questId, out var snapshot)) { return NotStartedRow(questId); } return MapQuestProgressRow(playerId, snapshot, deliveryStore); } internal static QuestProgressRowJson MapQuestProgressRow( string playerId, QuestStepState snapshot, IRewardDeliveryStore deliveryStore) { var counters = new Dictionary(snapshot.ObjectiveCounters, StringComparer.Ordinal); return snapshot.Status switch { QuestProgressStatus.Active => new QuestProgressRowJson { QuestId = snapshot.QuestId, Status = StatusActive, CurrentStepIndex = snapshot.CurrentStepIndex, ObjectiveCounters = counters, }, QuestProgressStatus.Completed => new QuestProgressRowJson { QuestId = snapshot.QuestId, Status = StatusCompleted, CurrentStepIndex = snapshot.CurrentStepIndex, ObjectiveCounters = counters, CompletedAt = snapshot.CompletedAt, CompletionRewardSummary = MapCompletionRewardSummary(playerId, snapshot.QuestId, deliveryStore), }, _ => throw new InvalidOperationException($"Unknown quest progress status '{snapshot.Status}'."), }; } private static QuestCompletionRewardSummaryJson? MapCompletionRewardSummary( string playerId, string questId, IRewardDeliveryStore deliveryStore) { if (!deliveryStore.TryGet(playerId, questId, out var deliveryEvent)) { return null; } return new QuestCompletionRewardSummaryJson { ItemGrants = MapItemGrants(deliveryEvent.GrantedItems), SkillXpGrants = MapSkillXpGrants(deliveryEvent.GrantedSkillXp), }; } /// Preserves commit-time grant order from . private static List MapItemGrants(IReadOnlyList grants) { var summary = new List(grants.Count); foreach (var grant in grants) { summary.Add( new QuestItemGrantJson { ItemId = grant.ItemId, Quantity = grant.Quantity, }); } return summary; } /// Preserves commit-time grant order from . private static List MapSkillXpGrants(IReadOnlyList grants) { var summary = new List(grants.Count); foreach (var grant in grants) { summary.Add( new QuestSkillXpGrantJson { SkillId = grant.SkillId, Amount = grant.Amount, }); } return summary; } private static QuestProgressRowJson NotStartedRow(string questId) => new() { QuestId = questId, Status = StatusNotStarted, CurrentStepIndex = 0, ObjectiveCounters = new Dictionary(StringComparer.Ordinal), }; }