using SVSim.Database; using SVSim.Database.Repositories.Mission; namespace SVSim.EmulatedEntrypoint.Services; public sealed class MissionProgressService : IMissionProgressService { private readonly SVSimDbContext _db; private readonly IMissionCatalogRepository _catalog; private readonly IViewerMissionRepository _viewerRepo; private readonly TimeProvider _time; public MissionProgressService( SVSimDbContext db, IMissionCatalogRepository catalog, IViewerMissionRepository viewerRepo, TimeProvider time) { _db = db; _catalog = catalog; _viewerRepo = viewerRepo; _time = time; } public async Task RecordEventAsync(long viewerId, IReadOnlyList eventKeys, int delta = 1, CancellationToken ct = default) { if (eventKeys.Count == 0) return; var now = _time.GetUtcNow(); var periods = JstPeriod.AllPeriods(now); // 1. Increment counters for every (key, period). foreach (var key in eventKeys) { foreach (var period in periods) { await _viewerRepo.UpsertCounterAsync(viewerId, key, period, delta, ct); } } await _db.SaveChangesAsync(ct); // 2. Find catalog rows referencing any of these event keys; mark claimable on threshold. var achievements = await _catalog.GetAchievementsByEventTypesAsync(eventKeys, ct); if (achievements.Count > 0) { var byType = achievements.GroupBy(a => a.AchievementType).ToDictionary(g => g.Key, g => g.ToList()); foreach (var (achType, catalogRows) in byType) { var viewerRow = await _viewerRepo.GetAchievementAsync(viewerId, achType, ct); if (viewerRow is null) continue; var atLevel = catalogRows.FirstOrDefault(r => r.Level == viewerRow.Level); if (atLevel is null || atLevel.EventType is null) continue; var count = await _viewerRepo.GetCounterAsync(viewerId, atLevel.EventType, JstPeriod.AllTime, ct); if (count >= atLevel.RequireNumber && viewerRow.AchievementStatus == 0) { viewerRow.AchievementStatus = 1; } } await _db.SaveChangesAsync(ct); } } }