server-master/srcs/FamilyServer/Achievements/FamilyAchievementManager.cs
2026-02-10 18:21:30 +01:00

361 lines
No EOL
16 KiB
C#

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<int, FamilyAchievementSpecificConfiguration> _achievementsConfiguration;
private readonly ConcurrentQueue<FamilyAchievementIncrement> _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<FamilyUpdateMessage> _familyUpdatePublisher;
private readonly Dictionary<int, FamilyMissionSpecificConfiguration> _missionSpecificConfigurations;
private readonly ConcurrentQueue<FamilyMissionIncrement> _missionsQueue = new();
private readonly IMessagePublisher<FamilyAchievementUnlockedMessage> _unlockedMessagePublisher;
public FamilyAchievementManager(FamilyManager familyManager, FamilyAchievementsConfiguration configuration, IMessagePublisher<FamilyUpdateMessage> familyUpdatePublisher,
IMessagePublisher<FamilyAchievementUnlockedMessage> 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<long, FamilyDTO> 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<long, FamilyDTO> 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<int, FamilyMissionDto>();
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<long, FamilyDTO> 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<int, FamilyAchievementCompletionDto>();
family.Achievements.Progress ??= new Dictionary<int, FamilyAchievementProgressDto>();
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
});
}
}
}