using System; using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Excel; using Lumina.Excel.Sheets; using QuestionableCompanion.Data; namespace QuestionableCompanion.Services; public class MSQProgressionService { private readonly IDataManager dataManager; private readonly IPluginLog log; private readonly QuestDetectionService questDetectionService; private readonly IObjectTable objectTable; private readonly IFramework framework; private List? mainScenarioQuests; private Dictionary questNameCache = new Dictionary(); private Dictionary> questsByExpansion = new Dictionary>(); private static readonly uint[] MSQ_JOURNAL_GENRE_IDS = new uint[14] { 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u }; private const uint LAST_ARR_QUEST_ID = 65964u; private static readonly Dictionary JournalGenreToExpansion = new Dictionary { { 1u, MSQExpansionData.Expansion.ARealmReborn }, { 2u, MSQExpansionData.Expansion.ARealmReborn }, { 3u, MSQExpansionData.Expansion.Heavensward }, { 4u, MSQExpansionData.Expansion.Heavensward }, { 5u, MSQExpansionData.Expansion.Heavensward }, { 6u, MSQExpansionData.Expansion.Stormblood }, { 7u, MSQExpansionData.Expansion.Stormblood }, { 8u, MSQExpansionData.Expansion.Shadowbringers }, { 9u, MSQExpansionData.Expansion.Shadowbringers }, { 10u, MSQExpansionData.Expansion.Shadowbringers }, { 11u, MSQExpansionData.Expansion.Endwalker }, { 12u, MSQExpansionData.Expansion.Endwalker }, { 13u, MSQExpansionData.Expansion.Dawntrail }, { 14u, MSQExpansionData.Expansion.Dawntrail } }; public MSQProgressionService(IDataManager dataManager, IPluginLog log, QuestDetectionService questDetectionService, IObjectTable objectTable, IFramework framework) { this.dataManager = dataManager; this.log = log; this.questDetectionService = questDetectionService; this.objectTable = objectTable; this.framework = framework; InitializeMSQData(); framework.RunOnTick(delegate { DebugCurrentCharacterQuest(); }, default(TimeSpan), 60); } private void InitializeMSQData() { try { ExcelSheet questSheet = dataManager.GetExcelSheet(); if (questSheet == null) { return; } questSheet.Count(); int manualCount = 0; foreach (Quest item in questSheet) { _ = item; manualCount++; } List highIdQuests = questSheet.Where((Quest q) => q.RowId > 66000).ToList(); if (highIdQuests.Count > 0) { highIdQuests.First(); } foreach (IGrouping item2 in (from q in questSheet where q.RowId != 0 group q by q.JournalGenre.RowId into g orderby g.Key select g).Take(10)) { _ = item2; } mainScenarioQuests = (from q in questSheet where ((ReadOnlySpan)MSQ_JOURNAL_GENRE_IDS).Contains(q.JournalGenre.RowId) orderby q.RowId select q).ToList(); if (mainScenarioQuests.Count == 0) { return; } foreach (Quest quest in mainScenarioQuests.Take(20)) { try { quest.Expansion.Value.Name.ToString(); } catch (Exception) { } } foreach (IGrouping group in from q in mainScenarioQuests group q by q.JournalGenre.RowId into g orderby g.Key select g) { uint genreId = group.Key; JournalGenreToExpansion.GetValueOrDefault(genreId, MSQExpansionData.Expansion.ARealmReborn); group.First().JournalGenre.Value.Name.ToString(); string.Join(", ", from q in @group.Take(3) select q.RowId); } foreach (IGrouping item3 in from q in mainScenarioQuests group q by JournalGenreToExpansion.GetValueOrDefault(q.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn) into g orderby g.Key select g) { _ = item3; } MSQExpansionData.ClearQuests(); foreach (Quest quest2 in mainScenarioQuests) { string name = quest2.Name.ToString(); if (!string.IsNullOrEmpty(name)) { questNameCache[quest2.RowId] = name; } MSQExpansionData.Expansion expansion = JournalGenreToExpansion.GetValueOrDefault(quest2.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn); if (quest2.JournalGenre.RowId != 2 || quest2.RowId <= 65964) { MSQExpansionData.RegisterQuest(quest2.RowId, expansion); string shortName = MSQExpansionData.GetExpansionShortName(expansion); if (!questsByExpansion.ContainsKey(shortName)) { questsByExpansion[shortName] = new List(); } questsByExpansion[shortName].Add(quest2); } } foreach (MSQExpansionData.Expansion allExpansion in MSQExpansionData.GetAllExpansions()) { string shortName2 = MSQExpansionData.GetExpansionShortName(allExpansion); List quests = questsByExpansion.GetValueOrDefault(shortName2); if ((quests?.Count ?? 0) > 0 && quests != null) { string.Join(", ", from q in quests.Take(5) select q.RowId); } } } catch (Exception) { } } public (uint questId, string questName) GetLastCompletedMSQ(string characterName) { if (mainScenarioQuests == null || mainScenarioQuests.Count == 0) { return (questId: 0u, questName: "—"); } try { List completedQuests = questDetectionService.GetAllCompletedQuestIds(); Quest lastMSQ = (from q in mainScenarioQuests where completedQuests.Contains(q.RowId) orderby q.RowId descending select q).FirstOrDefault(); if (lastMSQ.RowId != 0) { string questName = questNameCache.GetValueOrDefault(lastMSQ.RowId, "Unknown Quest"); return (questId: lastMSQ.RowId, questName: questName); } } catch (Exception) { } return (questId: 0u, questName: "—"); } public float GetMSQCompletionPercentage() { if (mainScenarioQuests == null || mainScenarioQuests.Count == 0) { return 0f; } try { List completedQuests = questDetectionService.GetAllCompletedQuestIds(); return (float)mainScenarioQuests.Count((Quest q) => completedQuests.Contains(q.RowId)) / (float)mainScenarioQuests.Count * 100f; } catch (Exception) { return 0f; } } public int GetTotalMSQCount() { return mainScenarioQuests?.Count ?? 0; } public int GetCompletedMSQCount() { if (mainScenarioQuests == null || mainScenarioQuests.Count == 0) { return 0; } try { List completedQuests = questDetectionService.GetAllCompletedQuestIds(); return mainScenarioQuests.Count((Quest q) => completedQuests.Contains(q.RowId)); } catch (Exception) { return 0; } } public string GetQuestName(uint questId) { return questNameCache.GetValueOrDefault(questId, "Unknown Quest"); } public bool IsMSQ(uint questId) { return mainScenarioQuests?.Any((Quest q) => q.RowId == questId) ?? false; } public ExpansionInfo? GetExpansionForQuest(uint questId) { MSQExpansionData.Expansion expansion = MSQExpansionData.GetExpansionForQuest(questId); return new ExpansionInfo { Name = MSQExpansionData.GetExpansionName(expansion), ShortName = MSQExpansionData.GetExpansionShortName(expansion), MinQuestId = 0u, MaxQuestId = 0u, ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(expansion) }; } public List GetExpansions() { return (from exp in MSQExpansionData.GetAllExpansions() select new ExpansionInfo { Name = MSQExpansionData.GetExpansionName(exp), ShortName = MSQExpansionData.GetExpansionShortName(exp), MinQuestId = 0u, MaxQuestId = 0u, ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(exp) }).ToList(); } public (int completed, int total) GetExpansionProgress(string expansionShortName) { List completedQuests = questDetectionService.GetAllCompletedQuestIds(); List? obj = questsByExpansion.GetValueOrDefault(expansionShortName) ?? new List(); int completed = obj.Count((Quest q) => completedQuests.Contains(q.RowId)); int total = obj.Count; return (completed: completed, total: total); } public ExpansionInfo? GetCurrentExpansion() { try { List completedQuests = questDetectionService.GetAllCompletedQuestIds(); (MSQExpansionData.Expansion expansion, string debugInfo) currentExpansionFromGameWithDebug = MSQExpansionData.GetCurrentExpansionFromGameWithDebug(); MSQExpansionData.Expansion gameExpansion = currentExpansionFromGameWithDebug.expansion; string[] array = currentExpansionFromGameWithDebug.debugInfo.Split('\n'); for (int i = 0; i < array.Length; i++) { string.IsNullOrWhiteSpace(array[i]); } MSQExpansionData.Expansion analysisExpansion = MSQExpansionData.GetCurrentExpansion(completedQuests); array = MSQExpansionData.GetExpansionDetectionDebugInfo(completedQuests).Split('\n'); for (int i = 0; i < array.Length; i++) { string.IsNullOrWhiteSpace(array[i]); } MSQExpansionData.Expansion finalExpansion = gameExpansion; if (gameExpansion == MSQExpansionData.Expansion.ARealmReborn && analysisExpansion != MSQExpansionData.Expansion.ARealmReborn) { finalExpansion = analysisExpansion; } return new ExpansionInfo { Name = MSQExpansionData.GetExpansionName(finalExpansion), ShortName = MSQExpansionData.GetExpansionShortName(finalExpansion), MinQuestId = 0u, MaxQuestId = 0u, ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(finalExpansion) }; } catch (Exception) { return GetExpansions().FirstOrDefault(); } } public Dictionary GetExpansionProgressForCharacter(List completedQuestIds) { Dictionary result = new Dictionary(); foreach (ExpansionInfo exp in GetExpansions()) { List expansionQuests = questsByExpansion.GetValueOrDefault(exp.ShortName) ?? new List(); int completed = expansionQuests.Count((Quest q) => completedQuestIds.Contains(q.RowId)); result[exp.ShortName] = (completed, expansionQuests.Count); } return result; } public List GetAllMSQQuests() { return mainScenarioQuests ?? new List(); } public Dictionary GetExpansionProgress() { Dictionary result = new Dictionary(); List completedQuests = questDetectionService.GetAllCompletedQuestIds(); foreach (MSQExpansionData.Expansion expansion in MSQExpansionData.GetAllExpansions()) { ExpansionProgress progress = MSQExpansionData.GetExpansionProgress(completedQuests, expansion); result[progress.ExpansionName] = new ExpansionProgressInfo { ExpansionName = progress.ExpansionName, ShortName = progress.ExpansionShortName, TotalQuests = progress.ExpectedCount, CompletedQuests = progress.CompletedCount, Percentage = progress.Percentage }; } return result; } public Dictionary GetExpansionProgressForCharacter(List completedQuestIds) { Dictionary result = new Dictionary(); uint result2; List completedQuestIdsUint = (from id in completedQuestIds select uint.TryParse(id, out result2) ? result2 : 0u into id where id != 0 select id).ToList(); foreach (MSQExpansionData.Expansion expansion in MSQExpansionData.GetAllExpansions()) { ExpansionProgress progress = MSQExpansionData.GetExpansionProgress(completedQuestIdsUint, expansion); result[progress.ExpansionName] = new ExpansionProgressInfo { ExpansionName = progress.ExpansionName, ShortName = progress.ExpansionShortName, TotalQuests = progress.ExpectedCount, CompletedQuests = progress.CompletedCount, Percentage = progress.Percentage }; } return result; } public ExpansionInfo? GetCurrentExpansion(uint lastCompletedQuestId) { MSQExpansionData.Expansion expansion = MSQExpansionData.GetExpansionForQuest(lastCompletedQuestId); return new ExpansionInfo { Name = MSQExpansionData.GetExpansionName(expansion), ShortName = MSQExpansionData.GetExpansionShortName(expansion), MinQuestId = 0u, MaxQuestId = 0u, ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(expansion) }; } private MSQExpansionData.Expansion ConvertLuminaExpansionToOurs(uint luminaExpansionId) { return luminaExpansionId switch { 0u => MSQExpansionData.Expansion.ARealmReborn, 1u => MSQExpansionData.Expansion.Heavensward, 2u => MSQExpansionData.Expansion.Stormblood, 3u => MSQExpansionData.Expansion.Shadowbringers, 4u => MSQExpansionData.Expansion.Endwalker, 5u => MSQExpansionData.Expansion.Dawntrail, _ => MSQExpansionData.Expansion.ARealmReborn, }; } public void DebugCurrentCharacterQuest() { try { IPlayerCharacter player = objectTable.LocalPlayer; if (player == null) { framework.RunOnTick(delegate { DebugCurrentCharacterQuest(); }, default(TimeSpan), 60); return; } _ = player.Name.TextValue; player.HomeWorld.Value.Name.ToString(); ExcelSheet questSheet = dataManager.GetExcelSheet(); if (questSheet == null) { return; } List completedMSQQuests = new List(); foreach (Quest quest in questSheet) { if (((ReadOnlySpan)MSQ_JOURNAL_GENRE_IDS).Contains(quest.JournalGenre.RowId) && QuestManager.IsQuestComplete((ushort)quest.RowId)) { completedMSQQuests.Add(quest); } } if (completedMSQQuests.Count == 0) { return; } Quest questData = completedMSQQuests.OrderByDescending((Quest q) => q.RowId).First(); try { questData.JournalGenre.Value.Name.ToString(); } catch { } try { questData.Expansion.Value.Name.ToString(); } catch { } foreach (Quest item in completedMSQQuests.OrderByDescending((Quest q) => q.RowId).Take(10).ToList()) { _ = item; } foreach (IGrouping item2 in from q in completedMSQQuests group q by JournalGenreToExpansion.GetValueOrDefault(q.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn) into g orderby g.Key select g) { _ = item2; } } catch (Exception) { } } }