using NeonSprawl.Server.Game.Factions; 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, IFactionStandingStore standingStore, IReputationDeltaStore auditStore, 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, standingStore, auditStore, 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), }; }