using Microsoft.EntityFrameworkCore; using RadicadeServer.API.Storefront; using RadicadeServer.DB; using RadicadeServer.Extensions; using RadicadeServer.RecNet.Avatar; using RadicadeServer.RecNet.Enums.Store; using RadicadeServer.RecNet.Progression; namespace RadicadeServer.Utils { public static class GameRewardRedemption { public static async Task TakeOutstandingAsync( RecDBContext db, long playerId, GiftContext context, string? message = null, Action? trace = null) { var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId); var json = data?.GetProgressionJson(); var pending = json?.PendingGameRewards; var outstanding = pending?.Where(r => !r.Consumed).ToList() ?? new List(); trace?.Invoke($"stage=OutstandingReward claimed={RewardTrace.Context(context)} " + $"rewards={pending?.Count ?? 0} outstanding={outstanding.Count}"); if (data == null || json == null || outstanding.Count == 0) return null; foreach (var candidate in outstanding) { var held = candidate.GiftDrop1 ?? candidate.GiftDrop2 ?? candidate.GiftDrop3; trace?.Invoke($"stage=OutstandingCandidate selectionId={candidate.RewardSelectionId} " + $"rewardType={candidate.RewardType} context={RewardTrace.Context(candidate.GiftContext)} " + $"box={RewardTrace.Box(held)}"); } var reward = outstanding.FirstOrDefault(r => r.GiftContext == context); var matchedBy = reward == null ? null : "context"; if (reward == null && RroRewardPools.RoomForContext(context) is { } room) { reward = outstanding.FirstOrDefault(r => RroRewardPools.RoomForContext(r.GiftContext) == room); matchedBy = reward == null ? null : $"room={room}"; } if (reward == null) { reward = outstanding[0]; matchedBy = "oldest outstanding"; } var drop = reward.GiftDrop1 ?? reward.GiftDrop2 ?? reward.GiftDrop3; if (drop == null) { trace?.Invoke($"stage=OutstandingRewardUnusable selectionId={reward.RewardSelectionId} " + "has no gift drop at all; nothing can be handed over from it."); return null; } var gift = drop.Clone(); if (!string.IsNullOrWhiteSpace(message)) gift.Tooltip = message; if (gift.GiftDropId == 0) gift.GiftDropId = StorefrontShared.NewGiftDropId(); reward.Consumed = true; reward.ConsumedGiftDropId = gift.GiftDropId; reward.ConsumedAt = DateTime.UtcNow; data.Progress = json.ToJson(); await db.SaveChangesAsync(); trace?.Invoke($"stage=OutstandingRewardMatched by={matchedBy} selectionId={reward.RewardSelectionId} " + $"rewardType={reward.RewardType} context={RewardTrace.Context(reward.GiftContext)} " + $"box={RewardTrace.Box(gift)}"); return gift; } public static async Task BuildRoomRewardAsync( RecDBContext db, long playerId, GiftContext context, string? message = null, Action? trace = null, string? catalogueJson = null) { var room = RroRewardPools.RoomForContext(context); if (room == null) return null; var owned = await LevelUpGiftService.CollectOwnedKeysAsync(db, playerId, context); Gift? box = RroRewardPools.BuildGift(context, owned, catalogueJson: catalogueJson); var paidFrom = context; if (box == null) { foreach (var weaker in RroRewardPools.LowerRanksOf(context)) { box = RroRewardPools.BuildGift(weaker, owned, catalogueJson: catalogueJson); if (box == null) continue; paidFrom = weaker; trace?.Invoke($"stage=RoomBoxFallbackRank context={RewardTrace.Context(context)} " + $"nothing left unowned, so the same quest's {RewardTrace.Context(weaker)} is what it pays."); break; } } if (box == null) { box = RroRewardPools.BuildFallbackDrops(selection: false, owned: owned).FirstOrDefault(); if (box != null) { paidFrom = RroRewardPools.ReacquisitionContext; trace?.Invoke($"stage=RoomBoxFallbackItem context={RewardTrace.Context(context)} " + "every reward the activity pays is owned; paying the non-exclusive item pool."); } } if (box == null) return null; box.GiftDropId = StorefrontShared.NewGiftDropId(); if (!string.IsNullOrWhiteSpace(message)) box.Tooltip = message; trace?.Invoke($"stage=RoomBox activity={room} context={RewardTrace.Context(context)} " + $"paidFrom={RewardTrace.Context(paidFrom)} box={RewardTrace.Box(box)}"); await RememberHandoverAsync(db, playerId, context, box, trace); return box; } public static async Task RememberHandoverAsync( RecDBContext db, long playerId, GiftContext context, Gift box, Action? trace = null) { var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId); if (data == null) { data = new ProgressionData { PlayerId = playerId }; db.Progression.Add(data); } var json = data.GetProgressionJson() ?? new ProgressionJson(); json.PendingGameRewards ??= new List(); json.PendingGameRewards.Add(new RecNet.Storefront.GameReward { RewardSelectionId = StorefrontShared.NewGiftDropId(), Message = box.Tooltip ?? string.Empty, GiftContext = context, RewardType = RroRewardPools.IsQuestContext(context) ? RewardType.PostQuestActivity : RewardType.PostGameActivity, GiftDrop1 = box.Clone(), CreatedAt = DateTime.UtcNow, Consumed = true, ConsumedGiftDropId = box.GiftDropId, ConsumedAt = DateTime.UtcNow }); data.Progress = json.ToJson(); await db.SaveChangesAsync(); trace?.Invoke($"stage=HandoverRecorded context={RewardTrace.Context(context)} " + $"box={RewardTrace.Box(box)} the completion is recorded as delivered, so a later request " + "for it cannot pay a second box."); } public static async Task TakePendingBoxAsync( RecDBContext db, long playerId, GiftContext context, Action? trace = null) { var room = RroRewardPools.RoomForContext(context); if (room == null || !RroRewardPools.IsRewardActivity(room)) return null; var boxes = await StorefrontShared.GetPendingGiftsAsync(db, playerId); var box = boxes.FirstOrDefault(b => b.Context == context) ?? boxes.FirstOrDefault(b => RroRewardPools.RoomForContext(b.Context) == room); trace?.Invoke($"stage=PendingBox claimed={RewardTrace.Context(context)} room={room} " + $"held={boxes.Count} match={RewardTrace.Box(box)}"); if (box == null) return null; var gift = box.Clone(); if (gift.GiftDropId == 0) gift.GiftDropId = StorefrontShared.NewGiftDropId(); return gift; } public static async Task TakeRecentlyHandedOverAsync( RecDBContext db, long playerId, GiftContext context, TimeSpan within, Action? trace = null) { var data = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId); var json = data?.GetProgressionJson(); var pending = json?.PendingGameRewards; if (pending == null || pending.Count == 0) return null; var cutoff = DateTime.UtcNow - within; var taken = pending .Where(r => r.Consumed && r.ConsumedAt is { } at && at >= cutoff) .Where(r => r.GiftContext == context || (RroRewardPools.RoomForContext(context) is { } room && RroRewardPools.RoomForContext(r.GiftContext) == room)) .OrderByDescending(r => r.ConsumedAt) .FirstOrDefault(); if (taken == null) { trace?.Invoke($"stage=HandedOver claimed={RewardTrace.Context(context)} none within {within}."); return null; } var drop = taken.GiftDrop1 ?? taken.GiftDrop2 ?? taken.GiftDrop3; if (drop == null) return null; var gift = drop.Clone(); gift.GiftDropId = taken.ConsumedGiftDropId != 0 ? taken.ConsumedGiftDropId : gift.GiftDropId; trace?.Invoke($"stage=HandedOver claimed={RewardTrace.Context(context)} " + $"selectionId={taken.RewardSelectionId} box={RewardTrace.Box(gift)} - answering the retry " + "with the box it was given."); return gift; } } }