using Microsoft.EntityFrameworkCore; using RadicadeServer.API.Notifications; using RadicadeServer.API.Storefront; using RadicadeServer.DB; using RadicadeServer.Extensions; using RadicadeServer.RecNet.Enums.Messages; using RadicadeServer.RecNet.Enums.Store; using RadicadeServer.RecNet.Notifications; using RadicadeServer.RecNet.Progression; namespace RadicadeServer.Utils { public static class RoomProgressionService { public const int XpPerInterval = 10; public static readonly TimeSpan Interval = TimeSpan.FromMinutes(5); public const int RoomXpCap = 60; public const int DailyXpCap = 150; public static readonly TimeSpan DailyCapCooldown = TimeSpan.FromHours(24); public const string ProgressionLevelUpdateNotificationId = "PlayerProgressionLevelUpdate"; public static async Task AwardRoomXpAsync(RecDBContext db, long playerId, long roomId, bool isPublicRoom) { if (playerId <= 0 || roomId <= 0 || !isPublicRoom) return 0; var progression = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId); if (progression == null) { progression = new ProgressionData { PlayerId = playerId }; await db.Progression.AddAsync(progression); } var json = progression.GetProgressionJson() ?? new ProgressionJson(); var now = DateTime.UtcNow; if (json.XpEarnedTodayDate != now.Date) { json.XpEarnedToday = 0; json.XpEarnedTodayDate = now.Date; json.XpDailyCapReachedAt = null; } if (json.XpEarnedToday >= DailyXpCap) { if (json.XpDailyCapReachedAt is { } reachedAt && now >= reachedAt.Add(DailyCapCooldown)) { json.XpEarnedToday = 0; json.XpEarnedTodayDate = now.Date; json.XpDailyCapReachedAt = null; } else { json.XpDailyCapReachedAt ??= now; progression.Progress = json.ToJson(); await db.SaveChangesAsync(); return 0; } } if (json.RoomXpRoomId != roomId) { json.RoomXpRoomId = roomId; json.RoomXpEarned = 0; json.RoomXpLastGrantAt = now; progression.Progress = json.ToJson(); await db.SaveChangesAsync(); return 0; } if (json.RoomXpEarned >= RoomXpCap) { progression.Progress = json.ToJson(); await db.SaveChangesAsync(); return 0; } json.RoomXpLastGrantAt ??= now; int granted = 0; while (now - json.RoomXpLastGrantAt.Value >= Interval) { int roomHeadroom = RoomXpCap - (json.RoomXpEarned + granted); int dailyHeadroom = DailyXpCap - (json.XpEarnedToday + granted); int instalment = Math.Min(XpPerInterval, Math.Min(roomHeadroom, dailyHeadroom)); if (instalment <= 0) break; granted += instalment; json.RoomXpLastGrantAt = json.RoomXpLastGrantAt.Value.Add(Interval); } if (granted == 0 && now - json.RoomXpLastGrantAt.Value >= Interval) json.RoomXpLastGrantAt = now; if (granted == 0) { progression.Progress = json.ToJson(); await db.SaveChangesAsync(); return 0; } int applied = (int)Math.Min(granted, int.MaxValue); json.RoomXpEarned += applied; json.XpEarnedToday += applied; json.XpEarnedTodayDate = now.Date; json.LastXpEarnedAt = now; if (json.XpEarnedToday >= DailyXpCap) json.XpDailyCapReachedAt ??= now; int oldLevel = progression.Level; progression.XP = (int)Math.Min(progression.XP + applied, int.MaxValue); progression.Progress = json.ToJson(); await db.SaveChangesAsync(); await StorefrontShared.SendXpBalanceUpdateAsync(playerId, LevelUpGiftService.XpIntoCurrentLevel(progression), applied, StorefrontBalanceAddTypes.DirectBalanceWithMultiplier); int computedLevel = LevelUpGiftService.ComputeLevel(LevelUpGiftService.ParseMap(), progression.XP); if (computedLevel > progression.Level) progression.Level = computedLevel; await LevelUpGiftService.DeliverLevelUpGiftsAsync(db, playerId, recomputeFromXp: true, fromLevel: oldLevel, notify: true); await SendProgressionLevelUpdateAsync(db, playerId, progression); if (progression.Level > oldLevel) { await PushProgressionUpdateAsync(db, playerId); } return applied; } public static async Task SendProgressionLevelUpdateAsync(RecDBContext db, long playerId, ProgressionData? progression = null) { if (playerId <= 0) return; try { progression ??= await db.Progression.AsNoTracking() .FirstOrDefaultAsync(x => x.PlayerId == playerId); if (progression == null) return; var notification = new Notification { Id = ProgressionLevelUpdateNotificationId, Msg = new PlayerProgressionLevelUpdateNotification(progression) }; await Notify.SendNotify(playerId, notification); await Notify.SendToSubscribers(playerId, notification); } catch (Exception) { } } public static async Task PushProgressionUpdateAsync(RecDBContext db, long playerId) { await SendProgressionLevelUpdateAsync(db, playerId); try { var profile = await db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == playerId); if (profile == null) return; var plrData = await db.Players.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == playerId); var progression = await db.Progression.AsNoTracking().FirstOrDefaultAsync(x => x.PlayerId == playerId); if (progression != null) await StorefrontShared.SendXpBalanceUpdateAsync(playerId, LevelUpGiftService.XpIntoCurrentLevel(progression), 0, StorefrontBalanceAddTypes.LevelUp); await Notify.SendNotify(playerId, new Notification { Id = "AccountUpdate", Msg = profile }); await Notify.UpdateSubscriptions(playerId, Notifs.SubscriptionUpdateProfile, profile); if (plrData != null) await Notify.SendNotify(playerId, new Notification { Id = "SelfAccountUpdate", Msg = PlayerUtils.BuildSelfAccount(profile, plrData) }); } catch (Exception) { } } } }