using System; using System.Globalization; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Shared; using Questionable.Functions; using Questionable.Model.Questing; using Questionable.Navigation; namespace Questionable.Controller; internal sealed class SeasonalDutyController : MiniTaskController { private readonly MovementController _movementController; private readonly GameFunctions _gameFunctions; private readonly IClientState _clientState; private readonly SmartNavRouteEnqueuer _routeEnqueuer; private SeasonalDutyDefinition? _currentDuty; private int _completedCycles; private int? _cycleLimit; private DateTime _startTime; public bool IsRunning => _currentDuty != null; public TaskQueue TaskQueue => _taskQueue; public SeasonalDutyDefinition? CurrentDuty => _currentDuty; public int CompletedCycles => _completedCycles; public int? CycleLimit => _cycleLimit; public TimeSpan ElapsedTime { get { if (!IsRunning) { return TimeSpan.Zero; } return DateTime.UtcNow - _startTime; } } public SeasonalDutyController(IChatGui chatGui, ICondition condition, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, Configuration configuration, ILogger logger, MovementController movementController, GameFunctions gameFunctions, IClientState clientState, SmartNavRouteEnqueuer routeEnqueuer) : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, configuration, logger) { _movementController = movementController; _gameFunctions = gameFunctions; _clientState = clientState; _routeEnqueuer = routeEnqueuer; } public void Start(SeasonalDutyDefinition duty, int? cycleLimit = null) { _currentDuty = duty; _completedCycles = 0; _cycleLimit = cycleLimit; _startTime = DateTime.UtcNow; _logger.LogInformation("Starting seasonal duty farming: {DutyName} (limit: {Limit})", duty.Name, cycleLimit?.ToString(CultureInfo.InvariantCulture) ?? "unlimited"); EnqueueDutyCycle(); } public void Update() { if (_currentDuty == null || _movementController.IsPathfinding || _movementController.IsPathRunning || _gameFunctions.IsOccupied()) { return; } if (_taskQueue.AllTasksComplete) { _completedCycles++; if (_cycleLimit.HasValue && _completedCycles >= _cycleLimit.Value) { _logger.LogInformation("Seasonal duty cycle limit reached ({Cycles}/{Limit})", _completedCycles, _cycleLimit.Value); Stop("Cycle limit reached"); return; } EnqueueDutyCycle(); } UpdateCurrentTask(); } private void EnqueueDutyCycle() { if (_currentDuty == null) { return; } SeasonalDutyDefinition duty = _currentDuty; _logger.LogDebug("Enqueuing seasonal duty cycle for {DutyName}", duty.Name); if (_clientState.TerritoryType != duty.DutyTerritoryId) { _routeEnqueuer.Enqueue(_taskQueue, duty.TerritoryId, duty.NpcPosition); _taskQueue.Enqueue(new Mount.UnmountTask()); bool interacted = false; _taskQueue.Enqueue(new WaitCondition.Task(delegate { if (_clientState.TerritoryType == _currentDuty.DutyTerritoryId) { return true; } if (!interacted) { interacted = _gameFunctions.InteractWith(duty.NpcDataId); } return false; }, $"InteractAndWait(territory: {duty.DutyTerritoryId})")); } _taskQueue.Enqueue(new ClearObjectsWithAction.ClearTask(duty.DataIds, duty.Action, duty.DutyTerritoryId, duty.StopDistance, duty.WaypointPositions)); _taskQueue.Enqueue(new WaitCondition.Task(() => _clientState.TerritoryType != _currentDuty.DutyTerritoryId, "Wait(duty end)")); _taskQueue.Enqueue(new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(3L))); } public override void Stop(string label) { base.Stop(label); if (_currentDuty != null) { _logger.LogInformation("Stopping seasonal duty farming: {Label} (completed {Cycles} cycles)", label, _completedCycles); _routeEnqueuer.ClearDestination(); _currentDuty = null; _completedCycles = 0; _cycleLimit = null; _taskQueue.Reset(); } } }