361 lines
No EOL
16 KiB
C#
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
|
|
});
|
|
}
|
|
}
|
|
} |