// WingsEmu // // Developed by NosWings Team using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PhoenixLib.Caching; using PhoenixLib.DAL.Redis.Locks; using PhoenixLib.Logging; using WingsAPI.Data.GameData; using WingsEmu.Core.Extensions; using WingsEmu.DTOs.Quests; using WingsEmu.Game.Quests; namespace Plugin.QuestImpl.Managers { public class QuestManager : IQuestManager { private readonly IExpirableLockService _lockService; private readonly ILongKeyCachedRepository _questCache; private readonly Dictionary> _questlines = new(); private readonly IKeyValueCache> _questNameCache; private readonly Dictionary _questNpcById = new(); private readonly IResourceLoader _questNpcResourceLoader; private readonly IResourceLoader _questResourceLoader; private readonly Dictionary _tutorialDataById = new(); private readonly Dictionary> _tutorialDataByScriptId = new(); private readonly Dictionary> _tutorialDataByType = new(); private readonly Dictionary _tutorialQuestPayScriptByQuestId = new(); private readonly IResourceLoader _tutorialResourceLoader; public QuestManager(ILongKeyCachedRepository questCache, IResourceLoader questResourceLoader, IResourceLoader tutorialResourceLoader, IResourceLoader questNpcResourceLoader, IExpirableLockService lockService, IKeyValueCache> questNameCache) { _questCache = questCache; _questResourceLoader = questResourceLoader; _questNpcResourceLoader = questNpcResourceLoader; _tutorialResourceLoader = tutorialResourceLoader; _lockService = lockService; _questNameCache = questNameCache; } public IReadOnlyCollection GetNpcBlueAlertQuests() => _questNpcById.Values.Where(s => s.QuestId != null).ToList(); public QuestNpcDto GetNpcBlueAlertQuestByQuestId(int questId) => _questNpcById.Values.FirstOrDefault(s => s.QuestId == questId); public async Task CanRefreshDailyQuests(long characterId) => await _lockService.TryAddTemporaryLockAsync($"game:locks:quest-daily-refresh:{characterId}", DateTime.UtcNow.Date.AddDays(1)); public async Task TryTakeDailyQuest(Guid masterAccId, int questPackId) => await _lockService.TryAddTemporaryLockAsync($"game:locks:quest-daily-master:{masterAccId}:{questPackId.ToString()}", DateTime.UtcNow.Date.AddDays(1)); public async Task TryTakeDailyQuest(long characterId, int questPackId) => await _lockService.TryAddTemporaryLockAsync($"game:locks:quest-daily-char:{characterId}:{questPackId.ToString()}", DateTime.UtcNow.Date.AddDays(1)); public bool IsNpcBlueAlertQuest(int questId) => _questNpcById.Values.Any(s => s.QuestId == questId); public List GetQuestByName(string name) => _questNameCache.Get(name); public async Task InitializeAsync() { int questCounter = 0; int scriptsCounter = 0; int objectivesCounter = 0; int prizesCounter = 0; int questsNpcCounter = 0; IEnumerable quests = await _questResourceLoader.LoadAsync(); IEnumerable tutorialScripts = await _tutorialResourceLoader.LoadAsync(); IEnumerable questsNpc = await _questNpcResourceLoader.LoadAsync(); var nextQuests = new Dictionary>(); foreach (QuestDto questDto in quests) { if (questDto.NextQuestId == -1) { continue; } if (!nextQuests.ContainsKey(questDto.Id)) { nextQuests.Add(questDto.Id, new List()); } nextQuests[questDto.Id].Add(questDto.NextQuestId); } nextQuests = BuildQuestline(nextQuests); foreach (QuestDto quest in quests) { _questlines.TryAdd(quest.Id, nextQuests.ContainsKey(quest.Id) ? nextQuests[quest.Id] : new List()); _questCache.Set(quest.Id, quest); _questNameCache.GetOrSet(quest.Name, () => new List()).Add(quest); if (quest.NextQuestId != -1) { if (!nextQuests.ContainsKey(quest.Id)) { nextQuests.Add(quest.Id, new List()); } nextQuests[quest.Id].Add(quest.NextQuestId); } questCounter++; objectivesCounter += quest.Objectives.Count; prizesCounter += quest.Prizes.Count; } foreach (TutorialDto tutorialDto in tutorialScripts) { if (!_tutorialDataByType.ContainsKey(tutorialDto.Type)) { _tutorialDataByType.Add(tutorialDto.Type, new List()); } if (!_tutorialDataByScriptId.ContainsKey(tutorialDto.ScriptId)) { _tutorialDataByScriptId.Add(tutorialDto.ScriptId, new List()); } if (tutorialDto.Type == TutorialActionType.WAIT_FOR_REWARDS_CLAIM) { _tutorialQuestPayScriptByQuestId.Add(tutorialDto.Data, tutorialDto); } _tutorialDataById.TryAdd(tutorialDto.Id, tutorialDto); _tutorialDataByType[tutorialDto.Type].Add(tutorialDto); _tutorialDataByScriptId[tutorialDto.ScriptId].Add(tutorialDto); ; scriptsCounter++; } foreach (QuestNpcDto questNpcDto in questsNpc) { _questNpcById.TryAdd(questNpcDto.Id, questNpcDto); questsNpcCounter++; } Log.Info($"[RESOURCES] Loaded {questCounter} quests."); Log.Info($"[RESOURCES] Loaded {objectivesCounter} quests objectives."); Log.Info($"[RESOURCES] Loaded {prizesCounter} quests rewards."); Log.Info($"[RESOURCES] Loaded {scriptsCounter} tutorial scripts."); Log.Info($"[RESOURCES] Loaded {questsNpcCounter} NPC quests."); } public QuestDto GetQuestById(int questId) => _questCache.Get(questId); public IReadOnlyCollection GetScriptsTutorialByType(TutorialActionType type) => _tutorialDataByType.ContainsKey(type) ? _tutorialDataByType[type] : Array.Empty(); public IReadOnlyCollection GetScriptsTutorialByScriptId(int scriptId) => _tutorialDataByScriptId.ContainsKey(scriptId) ? _tutorialDataByScriptId[scriptId] : Array.Empty(); public IReadOnlyCollection GetScriptsTutorial() => _tutorialDataById.Values; public IReadOnlyCollection GetScriptsTutorialUntilIndex(int scriptId, int scriptIndex) => _tutorialDataById.Values .Where(s => s.ScriptId < scriptId || s.ScriptId == scriptId && s.ScriptIndex <= scriptIndex) .OrderBy(s => s.ScriptId).ThenBy(s => s.ScriptIndex).ToList(); public IReadOnlyCollection GetQuestlines(int questId) => _questlines.ContainsKey(questId) ? _questlines[questId] : Array.Empty(); public TutorialDto GetQuestPayScriptByQuestId(int questId) => _tutorialQuestPayScriptByQuestId.GetOrDefault(questId); public TutorialDto GetScriptTutorialById(int scriptId) => _tutorialDataById.ContainsKey(scriptId) ? _tutorialDataById[scriptId] : null; public TutorialDto GetScriptTutorialByIndex(int scriptId, int index) => _tutorialDataById.FirstOrDefault(s => s.Value.ScriptId == scriptId && s.Value.ScriptIndex == index).Value; public TutorialDto GetFirstScriptFromIdByType(int scriptId, TutorialActionType type) => _tutorialDataByType.ContainsKey(type) ? _tutorialDataByType[type].FirstOrDefault(s => s.Id > scriptId) : null; public QuestNpcDto GetQuestNpcByScriptId(int scriptId) => _questNpcById.FirstOrDefault(s => s.Value.StartingScript == scriptId).Value; private static Dictionary> BuildQuestline(IDictionary> quests) { bool built = true; foreach ((int questId, List nextQuestIds) in quests.ToList()) { if (!quests.ContainsKey(questId)) { continue; } foreach (int nextQuestId in nextQuestIds.ToList()) { if (!quests.ContainsKey(nextQuestId)) { continue; } built = false; quests[questId].AddRange(quests[nextQuestId]); quests.Remove(nextQuestId); } } if (!built) { return BuildQuestline(quests); } var invertedDictionary = new Dictionary>(); foreach ((int questId, List nextQuestIds) in quests) { foreach (int nextQuestId in nextQuestIds) { if (!invertedDictionary.ContainsKey(nextQuestId)) { invertedDictionary.Add(nextQuestId, new List()); } invertedDictionary[nextQuestId].Add(questId); } invertedDictionary.Add(questId, new List { questId }); } return invertedDictionary; } } }