using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Services; using Newtonsoft.Json.Linq; namespace QuestionableCompanion.Services; public class ARPostProcessEventQuestService : IDisposable { private readonly IDalamudPluginInterface pluginInterface; private readonly QuestionableIPC questionableIPC; private readonly EventQuestResolver eventQuestResolver; private readonly Configuration configuration; private readonly IPluginLog log; private readonly IFramework framework; private readonly ICommandManager commandManager; private readonly LifestreamIPC lifestreamIPC; private ICallGateSubscriber? characterAdditionalTaskSubscriber; private ICallGateSubscriber? characterPostProcessSubscriber; private Action? characterAdditionalTaskHandler; private Action? characterPostProcessHandler; private bool isProcessingEventQuests; private DateTime postProcessStartTime; private List currentQuestHierarchy = new List(); private string currentPluginName = string.Empty; private string lastTerritoryWaitDetected = string.Empty; private DateTime lastTerritoryTeleportTime = DateTime.MinValue; private const string PLUGIN_NAME = "QuestionableCompanion"; private const string AR_CHARACTER_ADDITIONAL_TASK = "AutoRetainer.OnCharacterAdditionalTask"; private const string AR_CHARACTER_POST_PROCESS_EVENT = "AutoRetainer.OnCharacterReadyForPostprocess"; private const string AR_FINISH_CHARACTER_POST_PROCESS = "AutoRetainer.FinishCharacterPostprocessRequest"; private const string AR_REQUEST_CHARACTER_POST_PROCESS = "AutoRetainer.RequestCharacterPostprocess"; public ARPostProcessEventQuestService(IDalamudPluginInterface pluginInterface, QuestionableIPC questionableIPC, EventQuestResolver eventQuestResolver, Configuration configuration, IPluginLog log, IFramework framework, ICommandManager commandManager, LifestreamIPC lifestreamIPC) { this.pluginInterface = pluginInterface; this.questionableIPC = questionableIPC; this.eventQuestResolver = eventQuestResolver; this.configuration = configuration; this.log = log; this.framework = framework; this.commandManager = commandManager; this.lifestreamIPC = lifestreamIPC; InitializeIPC(); } private void InitializeIPC() { try { characterAdditionalTaskSubscriber = pluginInterface.GetIpcSubscriber("AutoRetainer.OnCharacterAdditionalTask"); if (characterAdditionalTaskSubscriber == null) { return; } characterAdditionalTaskHandler = delegate { try { RegisterWithAutoRetainer(); } catch { } }; characterAdditionalTaskSubscriber.Subscribe(characterAdditionalTaskHandler); characterPostProcessSubscriber = pluginInterface.GetIpcSubscriber("AutoRetainer.OnCharacterReadyForPostprocess"); if (characterPostProcessSubscriber == null) { return; } characterPostProcessHandler = delegate(string pluginName) { try { OnARCharacterPostProcessStarted(pluginName); } catch { } }; characterPostProcessSubscriber.Subscribe(characterPostProcessHandler); } catch { } } private void RegisterWithAutoRetainer() { try { pluginInterface.GetIpcSubscriber("AutoRetainer.RequestCharacterPostprocess").InvokeAction("QuestionableCompanion"); } catch { } } private void OnARCharacterPostProcessStarted(string pluginName) { try { if (pluginName != "QuestionableCompanion") { return; } if (!configuration.RunEventQuestsOnARPostProcess) { FinishPostProcess(); return; } currentPluginName = pluginName; postProcessStartTime = DateTime.Now; framework.RunOnFrameworkThread(async delegate { try { await ProcessEventQuestsAsync(); } catch { FinishPostProcess(); } }); } catch { try { FinishPostProcess(); } catch { } } } private async Task ProcessEventQuestsAsync() { if (isProcessingEventQuests) { return; } isProcessingEventQuests = true; bool shouldFinishPostProcess = false; try { _ = 1; try { List detectedEventQuests = DetectActiveEventQuests(); if (detectedEventQuests.Count == 0) { shouldFinishPostProcess = true; return; } currentQuestHierarchy = new List(detectedEventQuests); await ImportEventQuestsForPostProcess(detectedEventQuests); await WaitForEventQuestsCompletion(detectedEventQuests); shouldFinishPostProcess = true; } catch { shouldFinishPostProcess = true; } } finally { if (shouldFinishPostProcess) { await ClearPriorityQuests(); FinishPostProcess(); } isProcessingEventQuests = false; } } private List DetectActiveEventQuests() { try { return questionableIPC.GetCurrentlyActiveEventQuests() ?? new List(); } catch { return new List(); } } private async Task ImportEventQuestsForPostProcess(List detectedEventQuests) { List allQuestsToImport = new List(); try { foreach (string questId in detectedEventQuests) { foreach (string quest in await GetQuestHierarchy(questId)) { if (!allQuestsToImport.Contains(quest)) { allQuestsToImport.Add(quest); } } } if (!questionableIPC.IsAvailable) { return; } try { questionableIPC.ClearQuestPriority(); await Task.Delay(500); } catch { } foreach (string questId2 in allQuestsToImport) { try { questionableIPC.AddQuestPriority(questId2); await Task.Delay(100); } catch { } } await Task.Delay(500); try { await framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }); } catch { } } catch { throw; } } private async Task> GetQuestHierarchy(string questId) { List hierarchy = new List(); HashSet visited = new HashSet(); await CollectPrerequisitesRecursive(questId, hierarchy, visited); return hierarchy; } private async Task CollectPrerequisitesRecursive(string questId, List hierarchy, HashSet visited) { if (visited.Contains(questId)) { return; } visited.Add(questId); try { List prerequisites = eventQuestResolver.ResolveEventQuestDependencies(questId); if (prerequisites.Count > 0) { foreach (string prereq in prerequisites) { await CollectPrerequisitesRecursive(prereq, hierarchy, visited); } } hierarchy.Add(questId); } catch { hierarchy.Add(questId); } await Task.CompletedTask; } private async Task WaitForEventQuestsCompletion(List originalEventQuests) { TimeSpan maxWaitTime = TimeSpan.FromMinutes(configuration.EventQuestPostProcessTimeoutMinutes); DateTime startTime = DateTime.Now; TimeSpan checkInterval = TimeSpan.FromSeconds(2L); while (DateTime.Now - startTime < maxWaitTime) { try { CheckForTerritoryWait(); if (!questionableIPC.IsAvailable) { await Task.Delay(checkInterval); continue; } bool isRunning = questionableIPC.IsRunning(); List currentEventQuests = DetectActiveEventQuests(); if (originalEventQuests.Where((string q) => currentEventQuests.Contains(q)).ToList().Count == 0) { if (!isRunning) { break; } try { await framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst stop"); }); await Task.Delay(500); break; } catch { break; } } } catch { } await Task.Delay(checkInterval); } } private void CheckForTerritoryWait() { if (!questionableIPC.IsRunning()) { return; } object task = questionableIPC.GetCurrentTask(); if (task == null) { return; } try { if (!(task is JObject jObject)) { return; } JToken taskNameToken = jObject["TaskName"]; if (taskNameToken == null) { return; } string taskName = taskNameToken.ToString(); if (string.IsNullOrEmpty(taskName)) { return; } Match waitTerritoryMatch = new Regex("Wait\\(territory:\\s*(.+?)\\s*\\((\\d+)\\)\\)").Match(taskName); if (!waitTerritoryMatch.Success) { return; } string territoryName = waitTerritoryMatch.Groups[1].Value.Trim(); uint territoryId = uint.Parse(waitTerritoryMatch.Groups[2].Value); string territoryKey = $"{territoryName}_{territoryId}"; double timeSinceLastTeleport = (DateTime.Now - lastTerritoryTeleportTime).TotalSeconds; if (lastTerritoryWaitDetected == territoryKey && timeSinceLastTeleport < 60.0) { return; } lastTerritoryWaitDetected = territoryKey; lastTerritoryTeleportTime = DateTime.Now; framework.RunOnFrameworkThread(delegate { try { commandManager.ProcessCommand("/li " + territoryName); } catch { } }); } catch { } } private async Task ClearPriorityQuests() { try { if (questionableIPC.IsAvailable) { questionableIPC.ClearQuestPriority(); await Task.Delay(500); } } catch { } } private void FinishPostProcess() { try { pluginInterface.GetIpcSubscriber("AutoRetainer.FinishCharacterPostprocessRequest").InvokeAction(); } catch { } } public void Dispose() { try { if (characterAdditionalTaskHandler != null && characterAdditionalTaskSubscriber != null) { characterAdditionalTaskSubscriber.Unsubscribe(characterAdditionalTaskHandler); } if (characterPostProcessHandler != null && characterPostProcessSubscriber != null) { characterPostProcessSubscriber.Unsubscribe(characterPostProcessHandler); } } catch { } } }