using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using FamilyServer.Logs; using FamilyServer.Managers; using Microsoft.Extensions.Hosting; using PhoenixLib.DAL.Redis.Locks; using PhoenixLib.Logging; using PhoenixLib.ServiceBus; using Plugin.FamilyImpl.Achievements; using Plugin.FamilyImpl.Messages; using WingsAPI.Communication.Families; using WingsAPI.Data.Families; using WingsEmu.Packets.Enums.Families; namespace FamilyServer.Achievements { public class FamilyAchievementManager : BackgroundService { private readonly Dictionary _achievementsConfiguration; private readonly ConcurrentQueue _achievementsQueue = new(); private readonly FamilyAchievementsConfiguration _configuration; private readonly IExpirableLockService _expirableLockService; private readonly FamilyExperienceManager _familyExperienceManager; private readonly FamilyLogManager _familyLogManager; private readonly FamilyManager _familyManager; private readonly IFamilyService _familyService; private readonly IMessagePublisher _familyUpdatePublisher; private readonly Dictionary _missionSpecificConfigurations; private readonly ConcurrentQueue _missionsQueue = new(); private readonly IMessagePublisher _unlockedMessagePublisher; public FamilyAchievementManager(FamilyManager familyManager, FamilyAchievementsConfiguration configuration, IMessagePublisher familyUpdatePublisher, IMessagePublisher unlockedMessagePublisher, IFamilyService familyService, FamilyMissionsConfiguration missionsConfiguration, IExpirableLockService expirableLockService, FamilyExperienceManager familyExperienceManager, FamilyLogManager familyLogManager) { _familyManager = familyManager; _configuration = configuration; _familyUpdatePublisher = familyUpdatePublisher; _unlockedMessagePublisher = unlockedMessagePublisher; _familyService = familyService; _expirableLockService = expirableLockService; _familyExperienceManager = familyExperienceManager; _familyLogManager = familyLogManager; _missionSpecificConfigurations = missionsConfiguration.ToDictionary(s => s.MissionId); _achievementsConfiguration = configuration.Counters.ToDictionary(s => s.Id); } private static TimeSpan RefreshTime => TimeSpan.FromSeconds(Convert.ToInt32(Environment.GetEnvironmentVariable("FAMILY_ACHIEVEMENT_REFRESH_IN_SECONDS") ?? "5")); public void AddIncrementToQueue(FamilyAchievementIncrement message) { _achievementsQueue.Enqueue(message); } public void AddIncrementToQueue(FamilyMissionIncrement message) { _missionsQueue.Enqueue(message); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { Dictionary toUpdate = new(); await FlushPendingAchievements(toUpdate); await FlushPendingMissions(toUpdate); if (toUpdate.Count > 0) { foreach (FamilyDTO family in toUpdate.Values) { await _familyManager.SaveFamilyAsync(family); } await _familyUpdatePublisher.PublishAsync(new FamilyUpdateMessage { Families = toUpdate.Values.ToList(), ChangedInfoFamilyUpdate = ChangedInfoFamilyUpdate.AchievementsAndMissions }); } await Task.Delay(RefreshTime, stoppingToken); } } private async Task FlushPendingMissions(Dictionary toUpdate) { try { if (_missionsQueue.IsEmpty) { return; } HashSet<(long, int)> unlockedMissions = new(); while (_missionsQueue.TryDequeue(out FamilyMissionIncrement message)) { FamilyDTO family = await _familyManager.GetFamilyByFamilyIdAsync(message.FamilyId); if (family == null) { Log.Debug("Family not found"); continue; } int missionId = message.MissionId; int value = message.ValueToAdd; long? characterId = message.CharacterId; Log.Debug($"[FAMILY_MISSIONS] Family {family.Id} adding {value} to mission: {missionId}"); family.Missions ??= new FamilyMissionsDto(); family.Missions.Missions ??= new Dictionary(); if (!family.Missions.Missions.TryGetValue(missionId, out FamilyMissionDto progress)) { family.Missions.Missions[missionId] = progress = new FamilyMissionDto { Id = missionId }; } if (progress.CompletionDate.HasValue && progress.CompletionDate < DateTime.UtcNow) { Log.Debug($"[FAMILY_MISSIONS] {progress.CompletionDate.Value} need to wait reset"); continue; } if (!_missionSpecificConfigurations.TryGetValue(missionId, out FamilyMissionSpecificConfiguration config)) { Log.Error($"Family achievement config {missionId} is not configured", new Exception()); continue; } if (config.OncePerPlayerPerDay && !characterId.HasValue) { Log.Debug($"[FAMILY_MISSION] could not increment mission: {missionId} for family: {family.Id} because characterId is missing"); continue; } if (config.OncePerPlayerPerDay && !await _expirableLockService.TryAddTemporaryLockAsync($"game:locks:family:{family.Id}:missions:{missionId}:character:{characterId}", DateTime.UtcNow.Date.AddDays(1))) { Log.Debug($"[FAMILY_MISSION] {characterId} could not increment mission: {missionId} for family: {family.Id}, already done for today"); continue; } progress.Count += value; toUpdate.TryAdd(family.Id, family); if (progress.Count < config.Value) { Log.Debug($"[FAMILY_MISSIONS] Family {family.Id} tried to increment mission: {missionId} progress.Count < config.Value"); continue; } // mission done DateTime now = DateTime.UtcNow; progress.CompletionDate = now; progress.CompletionCount++; unlockedMissions.Add((family.Id, missionId)); if (config.Rewards != null) { foreach (FamilyMissionReward reward in config.Rewards) { if (reward.FamilyXp.HasValue) { _familyExperienceManager.AddExperienceIncrementRequest(new ExperienceIncrementRequest { FamilyId = family.Id, Experience = reward.FamilyXp.Value }); } } } if (progress.Count > config.Value) { progress.Count = config.Value; continue; } Log.Debug($"[FAMILY_MISSIONS] Family {family.Id} tried to increment mission: {missionId} progress.Count <= config.Value"); } foreach ((long familyId, int missionId) in unlockedMissions) { _familyLogManager.SaveFamilyLogs(new[] { new FamilyLogDto { FamilyLogType = FamilyLogType.FamilyMission, FamilyId = familyId, Actor = missionId.ToString(), Timestamp = DateTime.UtcNow } }); } } catch (Exception e) { Log.Error("[FAMILY_ACHIEVEMENT_MANAGER]", e); } } private async Task FlushPendingAchievements(Dictionary toUpdate) { try { if (_achievementsQueue.IsEmpty) { return; } HashSet<(long, int)> unlockedAchievements = new(); while (_achievementsQueue.TryDequeue(out FamilyAchievementIncrement message)) { FamilyDTO family = await _familyManager.GetFamilyByFamilyIdAsync(message.FamilyId); if (family == null) { Log.Debug("Family not found"); continue; } int achievementId = message.AchievementId; int value = message.ValueToAdd; Log.Debug($"[FAMILY_ACHIEVEMENT] Family {family.Id} adding {value} to achievement: {achievementId}"); family.Achievements ??= new FamilyAchievementsDto(); family.Achievements.Achievements ??= new Dictionary(); family.Achievements.Progress ??= new Dictionary(); if (family.Achievements?.Achievements?.ContainsKey(achievementId) == true) { AddNextAchievement(achievementId, family, value); continue; } if (!family.Achievements.Progress.TryGetValue(achievementId, out FamilyAchievementProgressDto progress)) { family.Achievements.Progress[achievementId] = progress = new FamilyAchievementProgressDto { Id = achievementId }; } if (!_achievementsConfiguration.TryGetValue(achievementId, out FamilyAchievementSpecificConfiguration config)) { Log.Error($"Family achievement config {achievementId} is not configured", new Exception($"[FAMILY_ACHIEVEMENT_CONFIG] {progress.Id} not configured")); continue; } if (config.RequiredId.HasValue && !family.Achievements.Achievements.ContainsKey(config.RequiredId.Value)) { Log.Debug($"[FAMILY_ACHIEVEMENT] Family {family.Id} tried to increment achievement: {achievementId} but didn't have {config.RequiredId.Value}"); continue; } progress.Count += value; toUpdate.TryAdd(family.Id, family); if (progress.Count < config.Value) { Log.Debug($"[FAMILY_ACHIEVEMENT] Family {family.Id} tried to increment achievement: {achievementId} progress.Count < config.Value"); continue; } // achievement unlocked family.Achievements.Achievements[achievementId] = new FamilyAchievementCompletionDto { Id = progress.Id, CompletionDate = DateTime.UtcNow }; (long Id, int achievementId) key = (family.Id, achievementId); if (!unlockedAchievements.Contains(key)) { unlockedAchievements.Add(key); } if (config.Rewards != null) { foreach (FamilyAchievementReward reward in config.Rewards) { if (reward.FamilyXp.HasValue) { _familyExperienceManager.AddExperienceIncrementRequest(new ExperienceIncrementRequest { FamilyId = family.Id, Experience = reward.FamilyXp.Value }); } if (reward.FamilyUpgradeCategory.HasValue && reward.UpgradeValue.HasValue && reward.UpgradeId.HasValue) { await _familyService.TryAddFamilyUpgrade(new FamilyUpgradeRequest { FamilyId = family.Id, FamilyUpgradeType = reward.FamilyUpgradeCategory.Value, UpgradeId = reward.UpgradeId.Value, Value = reward.UpgradeValue.Value }); } } } family.Achievements.Progress.Remove(achievementId); int progressCount = achievementId is >= 9018 and <= 9036 ? 0 : progress.Count; AddNextAchievement(achievementId, family, progressCount); } foreach ((long familyId, int achievementId) in unlockedAchievements) { await _unlockedMessagePublisher.PublishAsync(new FamilyAchievementUnlockedMessage { FamilyId = familyId, AchievementId = achievementId }); _familyLogManager.SaveFamilyLogs(new[] { new FamilyLogDto { FamilyLogType = FamilyLogType.FamilyAchievement, FamilyId = familyId, Actor = achievementId.ToString(), Timestamp = DateTime.UtcNow } }); } } catch (Exception e) { Log.Error("[FAMILY_ACHIEVEMENT_MANAGER]", e); } } private void AddNextAchievement(int achievementId, FamilyDTO family, int value) { FamilyAchievementSpecificConfiguration tmp = _configuration.Counters.FirstOrDefault(s => s.RequiredId == achievementId); if (tmp == null) { Log.Error($"[FAMILY_ACHIEVEMENT_MANAGER] Couldn't find next achievement for {achievementId} - familyId: {family.Id}", new Exception()); return; } _achievementsQueue.Enqueue(new FamilyAchievementIncrement { FamilyId = family.Id, AchievementId = tmp.Id, ValueToAdd = value }); } } }