using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Excel; using Lumina.Excel.Sheets; using Lumina.Extensions; using Newtonsoft.Json; using QuestShare.Services; using System.Numerics; namespace QuestShare.Common { internal static class GameQuestManager { public static List GameQuests { get; private set; } = new List(); public static List SharedCache { get; private set; } = new List(); private static byte LastStep = 0; private static uint LastQuestId = 0; public static void Initialize() { ClientState.Login += OnLogin; ClientState.Logout += OnLogout; if (GameQuests.Count == 0) { LoadQuests(); } Framework.Update += OnFrameworkUpdate; } public static void Dispose() { ClientState.Login -= OnLogin; ClientState.Logout -= OnLogout; Framework.Update -= OnFrameworkUpdate; } public static void LoadQuests() { var activeQuest = GetActiveQuest(); GameQuests.Clear(); // iterate through all quests in the game to see which ones are currently active foreach (var quest in SheetManager.QuestSheet) { if (quest.Id == "" || quest.RowId == 0) { continue; } if (QuestManager.GetQuestSequence(quest.RowId) > 0) { Log.Debug($"Adding quest {quest.RowId} to GameQuests - {quest.Name} - Progression {QuestManager.GetQuestSequence(quest.RowId)-1}"); GameQuests.Add(new GameQuest(quest.RowId)); } } if (activeQuest != null) { SetActiveFlag(activeQuest.QuestId); } } private static void OnLogin() { LoadQuests(); } private static void OnLogout(int code, int _) { GameQuests.Clear(); SharedCache.Clear(); } private static void OnFrameworkUpdate(IFramework framework) { var q = GetActiveQuest(); if (q != null && q.QuestId == LastQuestId) { var step = QuestManager.GetQuestSequence(q.QuestId); if (step != LastStep) { if (LastStep == 0xFF && step == 0) { // quest was completed Log.Debug($"Quest {q.QuestId} was completed"); // check for next quest in the chain if (q.QuestData.PreviousQuest.TryGetFirst(out var prevQuest)) { Log.Debug($"Previous quest in chain was {prevQuest.RowId}"); var nextQuest = SheetManager.QuestSheet.FirstOrDefault(qu => prevQuest.Value.RowId == q.QuestId); if (nextQuest.RowId != 0) { Log.Debug($"Next quest in chain is {nextQuest.RowId}"); GameQuests.Add(new GameQuest(nextQuest.RowId)); SetActiveFlag(nextQuest.RowId); } else { Log.Debug("No next quest in chain"); GameQuests.Remove(q); } } SocketClientService.DispatchUpdate(false); } else { LastStep = step; Log.Debug($"Quest step changed to {step}"); SocketClientService.DispatchUpdate(false); } } } else if (q != null) { LastQuestId = q.QuestId; LastStep = QuestManager.GetQuestSequence(q.QuestId); } } public static GameQuest? GetActiveQuest() { return GameQuests.FirstOrDefault(q => q.IsActive); } public static GameQuest GetQuestById(uint questId) { if (SharedCache.FirstOrDefault(q => q.QuestId == questId) is GameQuest quest) { return quest; } else { var newQuest = new GameQuest(questId); SharedCache.Add(newQuest); return newQuest; } } public static void SetActiveFlag(uint questId) { var quest = GameQuests.FirstOrDefault(q => q.QuestId == questId); if (quest != null) { quest.IsActive = true; // set all other quests to inactive foreach (var q in GameQuests) { if (q.QuestId != questId) { q.IsActive = false; } } } } } internal class GameQuest { public uint QuestId { get; init; } [JsonIgnore] public Lumina.Excel.Sheets.Quest QuestData { get; init; } public string QuestName => QuestData.Name.ExtractText(); public byte CurrentStep => QuestManager.GetQuestSequence(QuestId); public List QuestSteps { get; private set; } = []; public bool IsMainQuest => false; // TODO: Implement this public bool IsActive { get; set; } = false; public GameQuest(uint questId) { QuestId = questId; if (SheetManager.QuestSheet.TryGetRow(questId, out var quest)) { QuestData = quest; } var sheet = TextSheetForQuest(QuestData); for(uint i = 0; i < sheet.Count; i++) { var row = sheet.GetRow(i); if (row.Key.ExtractText().Contains("_TODO_") && row.Value.ExtractText() != "") { QuestSteps.Add(row.Value.ExtractText()); } } } public Map GetMapLocation() { return QuestData.TodoParams.FirstOrDefault(param => param.ToDoCompleteSeq == CurrentStep) .ToDoLocation.FirstOrDefault(location => location is not { RowId: 0 }).Value.Map.Value; } public MapLinkPayload GetMapLink(byte step) { var toDoData = QuestData.TodoParams.Select(t => t.ToDoLocation).ToList(); var data = toDoData.ElementAt(step).FirstOrDefault(); var coords = MapUtil.WorldToMap(new Vector3(data.Value.X, data.Value.Y, data.Value.Z), data.Value.Map.Value.OffsetX, data.Value.Map.Value.OffsetY, 0, data.Value.Map.Value.SizeFactor); var mapLink = new MapLinkPayload(data.Value.Territory.Value.RowId, data.Value.Map.Value.RowId, coords.X, coords.Y); return mapLink; } public void GetQuestDetail() { } internal static ExcelSheet TextSheetForQuest(Lumina.Excel.Sheets.Quest q) { var qid = q.Id.ToString(); Log.Debug($"Getting text sheet for quest {q.Id} - RowID: {q.RowId}"); var dir = qid.Substring(qid.Length - 5, 3); return DataManager.GetExcelSheet(name: $"quest/{dir}/{qid}"); } } }