diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index 951e1dc3f..2cc2b07aa 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Collections.Generic; using System.Numerics; using System.Threading; using Dalamud.Game.ClientState.Conditions; @@ -108,6 +109,10 @@ public enum ECurrentQuestType private readonly IToastGui _toastGui; private readonly ICommandManager _commandManager; private EAutomationType _automationType; + private static readonly TimeSpan PauseRequestLifetime = TimeSpan.FromMinutes(10); + private readonly object _pauseRequestGate = new(); + private readonly Dictionary _pauseRequests = new(StringComparer.Ordinal); + private bool _pauseEffective; private DateTime _lastAutoRefresh = DateTime.MinValue; /// True while recovering from the player's death (waiting for the return prompt / respawn). @@ -265,6 +270,49 @@ public QuestProgress? PendingQuest public Func? IsQuestWindowOpenFunction { private get; set; } = () => true; public bool IsRunning => !_taskQueue.AllTasksComplete; + public void SetPauseRequest(string owner, bool paused) + { + if (string.IsNullOrWhiteSpace(owner)) + throw new ArgumentException("A pause-request owner is required.", nameof(owner)); + lock (_pauseRequestGate) + { + if (paused) + _pauseRequests[owner] = DateTime.UtcNow.Add(PauseRequestLifetime); + else + _pauseRequests.Remove(owner); + } + } + + public bool IsPauseRequestEffective(string owner) + { + lock (_pauseRequestGate) + { + PruneExpiredPauseRequests(); + return !string.IsNullOrWhiteSpace(owner) && _pauseRequests.ContainsKey(owner) && _pauseEffective; + } + } + + private bool HasPauseRequests() + { + lock (_pauseRequestGate) + { + PruneExpiredPauseRequests(); + return _pauseRequests.Count > 0; + } + } + + private void PruneExpiredPauseRequests() + { + var now = DateTime.UtcNow; + foreach (var owner in _pauseRequests.Where(request => request.Value <= now).Select(request => request.Key).ToArray()) + _pauseRequests.Remove(owner); + } + + private void SetPauseEffective(bool effective) + { + lock (_pauseRequestGate) + _pauseEffective = effective; + } public TaskQueue TaskQueue => _taskQueue; public string? CurrentTaskState @@ -340,6 +388,13 @@ public void Update() if (IsQuestingStopped) return; + if (HasPauseRequests() && _taskQueue.CurrentTaskExecutor == null) + { + SetPauseEffective(true); + return; + } + SetPauseEffective(false); + UpdateCurrentQuest(); if (!_clientState.IsLoggedIn) diff --git a/Questionable/External/QuestionableIpc.cs b/Questionable/External/QuestionableIpc.cs index 38af5ae18..5590def8b 100644 --- a/Questionable/External/QuestionableIpc.cs +++ b/Questionable/External/QuestionableIpc.cs @@ -38,6 +38,8 @@ internal sealed class QuestionableIpc : IDisposable private const string IpcStartGathering = "Questionable.StartGathering"; private const string IpcStartGatheringComplex = "Questionable.StartGatheringComplex"; private const string IpcStop = "Questionable.Stop"; + private const string IpcSetPauseRequest = "Questionable.SetPauseRequest"; + private const string IpcIsPauseRequestEffective = "Questionable.IsPauseRequestEffective"; private const string IpcRedoLookup = "Questionable.RedoLookup"; private const string IpcRedoLookupIndex = "Questionable.RedoLookupIndex"; private readonly ICallGateProvider _addQuestPriority; @@ -69,6 +71,8 @@ internal sealed class QuestionableIpc : IDisposable private readonly ICallGateProvider _startQuest; private readonly ICallGateProvider _startSingleQuest; private readonly ICallGateProvider _stop; + private readonly ICallGateProvider _setPauseRequest; + private readonly ICallGateProvider _isPauseRequestEffective; public QuestionableIpc( QuestController questController, @@ -148,6 +152,11 @@ public QuestionableIpc( _stop = pluginInterface.GetIpcProvider(IpcStop); _stop.RegisterFunc(Stop); + _setPauseRequest = pluginInterface.GetIpcProvider(IpcSetPauseRequest); + _setPauseRequest.RegisterAction(questController.SetPauseRequest); + _isPauseRequestEffective = pluginInterface.GetIpcProvider(IpcIsPauseRequestEffective); + _isPauseRequestEffective.RegisterFunc(questController.IsPauseRequestEffective); + _redoUtil = redoUtil; _redoLookup = pluginInterface.GetIpcProvider(IpcRedoLookup); @@ -178,6 +187,8 @@ public void Dispose() _startGathering.UnregisterFunc(); _startGatheringComplex.UnregisterFunc(); _stop.UnregisterFunc(); + _setPauseRequest.UnregisterAction(); + _isPauseRequestEffective.UnregisterFunc(); _redoLookup.UnregisterFunc(); }