662 lines
20 KiB
C#
662 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Dalamud.Game.NativeWrapper;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using QuestionableCompanion.Models;
|
|
|
|
namespace QuestionableCompanion.Services;
|
|
|
|
public class ExecutionService : IDisposable
|
|
{
|
|
private readonly IPluginLog log;
|
|
|
|
private readonly Configuration config;
|
|
|
|
private readonly QuestionableIPC questionableIPC;
|
|
|
|
private readonly AutoRetainerIPC autoRetainerIPC;
|
|
|
|
private readonly QuestDetectionService questDetection;
|
|
|
|
private readonly IClientState clientState;
|
|
|
|
private readonly IGameGui gameGui;
|
|
|
|
private readonly IFramework framework;
|
|
|
|
private CharacterSafeWaitService? safeWaitService;
|
|
|
|
private QuestPreCheckService? preCheckService;
|
|
|
|
private DCTravelService? dcTravelService;
|
|
|
|
private int currentCharacterIndex;
|
|
|
|
private readonly HashSet<string> completedCharacters = new HashSet<string>();
|
|
|
|
private readonly HashSet<string> failedCharacters = new HashSet<string>();
|
|
|
|
private bool waitingForRelog;
|
|
|
|
private string targetRelogCharacter = string.Empty;
|
|
|
|
private DateTime relogStartTime = DateTime.MinValue;
|
|
|
|
private bool wasLoggedOutDuringRelog;
|
|
|
|
public ExecutionState CurrentState { get; private set; } = new ExecutionState();
|
|
|
|
public bool IsRunning { get; private set; }
|
|
|
|
public bool IsPaused { get; private set; }
|
|
|
|
public List<LogEntry> Logs { get; } = new List<LogEntry>();
|
|
|
|
public event Action<LogEntry>? LogAdded;
|
|
|
|
public event Action<ExecutionState>? StateChanged;
|
|
|
|
public ExecutionService(IPluginLog log, Configuration config, QuestionableIPC questionableIPC, AutoRetainerIPC autoRetainerIPC, QuestDetectionService questDetection, IClientState clientState, IGameGui gameGui, IFramework framework)
|
|
{
|
|
this.log = log;
|
|
this.config = config;
|
|
this.questionableIPC = questionableIPC;
|
|
this.autoRetainerIPC = autoRetainerIPC;
|
|
this.questDetection = questDetection;
|
|
this.clientState = clientState;
|
|
this.gameGui = gameGui;
|
|
this.framework = framework;
|
|
questDetection.QuestAccepted += OnQuestAccepted;
|
|
questDetection.QuestCompleted += OnQuestCompleted;
|
|
framework.Update += OnFrameworkUpdate;
|
|
AddLog(LogLevel.Info, "Execution service initialized");
|
|
}
|
|
|
|
private void OnFrameworkUpdate(IFramework framework)
|
|
{
|
|
if (!waitingForRelog)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
if ((DateTime.Now - relogStartTime).TotalSeconds > 60.0)
|
|
{
|
|
AddLog(LogLevel.Warning, "Relog timeout - moving to next character");
|
|
waitingForRelog = false;
|
|
failedCharacters.Add(targetRelogCharacter);
|
|
SwitchToNextCharacter();
|
|
}
|
|
else if (!clientState.IsLoggedIn)
|
|
{
|
|
if (!wasLoggedOutDuringRelog)
|
|
{
|
|
AddLog(LogLevel.Info, "[ExecutionService] Character logged out, waiting for relog...");
|
|
wasLoggedOutDuringRelog = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!clientState.IsLoggedIn || clientState.LocalPlayer == null || !IsNamePlateReady())
|
|
{
|
|
return;
|
|
}
|
|
string currentChar = autoRetainerIPC.GetCurrentCharacter();
|
|
if (string.IsNullOrEmpty(currentChar) || !(currentChar == targetRelogCharacter))
|
|
{
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Success, "[ExecutionService] Relog confirmed: " + currentChar);
|
|
AddLog(LogLevel.Info, "[ExecutionService] NamePlate ready, character fully loaded");
|
|
if (config.EnableSafeWaitAfterCharacterSwitch && safeWaitService != null)
|
|
{
|
|
AddLog(LogLevel.Info, "[SafeWait] Stabilizing after character switch...");
|
|
safeWaitService.PerformQuickSafeWait();
|
|
AddLog(LogLevel.Info, "[SafeWait] Post-switch stabilization complete");
|
|
}
|
|
if (config.EnableQuestPreCheck && preCheckService != null)
|
|
{
|
|
AddLog(LogLevel.Info, "[PreCheck] Scanning quest status for current character...");
|
|
preCheckService.ScanCurrentCharacterQuestStatus();
|
|
}
|
|
waitingForRelog = false;
|
|
CurrentState.CurrentCharacter = currentChar;
|
|
questDetection.ResetTracking();
|
|
AddLog(LogLevel.Info, "[ExecutionService] Refreshing quest cache...");
|
|
questDetection.RefreshQuestCache();
|
|
NotifyStateChanged();
|
|
if (dcTravelService != null && dcTravelService.ShouldPerformDCTravel())
|
|
{
|
|
AddLog(LogLevel.Warning, "[DCTravel] DC Travel required for this character!");
|
|
AddLog(LogLevel.Info, "[DCTravel] Initiating DC travel before quest execution...");
|
|
Task.Run(async delegate
|
|
{
|
|
try
|
|
{
|
|
if (await dcTravelService.PerformDCTravel())
|
|
{
|
|
AddLog(LogLevel.Success, "[DCTravel] DC travel completed - starting quests");
|
|
ExecuteQuestsForCurrentCharacter();
|
|
}
|
|
else
|
|
{
|
|
AddLog(LogLevel.Error, "[DCTravel] DC travel failed - switching character");
|
|
SwitchToNextCharacter();
|
|
}
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
AddLog(LogLevel.Error, "[DCTravel] Error: " + ex2.Message);
|
|
SwitchToNextCharacter();
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
ExecuteQuestsForCurrentCharacter();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[ExecutionService] Error in relog monitoring: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
public bool Start()
|
|
{
|
|
QuestProfile profile = config.GetActiveProfile();
|
|
if (profile == null)
|
|
{
|
|
AddLog(LogLevel.Error, "No active profile selected");
|
|
return false;
|
|
}
|
|
if (profile.Characters.Count == 0)
|
|
{
|
|
AddLog(LogLevel.Error, "No characters configured in profile");
|
|
return false;
|
|
}
|
|
IsRunning = true;
|
|
IsPaused = false;
|
|
CurrentState.ActiveProfile = profile.Name;
|
|
CurrentState.Status = ExecutionStatus.Running;
|
|
AddLog(LogLevel.Success, "Started profile: " + profile.Name);
|
|
AddLog(LogLevel.Info, $"Characters in rotation: {profile.Characters.Count}");
|
|
currentCharacterIndex = 0;
|
|
SwitchToCharacter(profile.Characters[0]);
|
|
NotifyStateChanged();
|
|
return true;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
IsRunning = false;
|
|
IsPaused = false;
|
|
waitingForRelog = false;
|
|
if (questionableIPC.IsRunning())
|
|
{
|
|
questionableIPC.Stop();
|
|
}
|
|
CurrentState.Status = ExecutionStatus.Idle;
|
|
CurrentState.CurrentQuestId = 0u;
|
|
CurrentState.CurrentQuestName = string.Empty;
|
|
CurrentState.CurrentSequence = string.Empty;
|
|
AddLog(LogLevel.Info, "Execution stopped");
|
|
NotifyStateChanged();
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
IsPaused = true;
|
|
questionableIPC.Stop();
|
|
CurrentState.Status = ExecutionStatus.Waiting;
|
|
AddLog(LogLevel.Warning, "Execution paused");
|
|
NotifyStateChanged();
|
|
}
|
|
|
|
public void Resume()
|
|
{
|
|
IsPaused = false;
|
|
CurrentState.Status = ExecutionStatus.Running;
|
|
AddLog(LogLevel.Info, "Execution resumed");
|
|
NotifyStateChanged();
|
|
}
|
|
|
|
private void OnQuestAccepted(uint questId, string questName)
|
|
{
|
|
if (!IsRunning || IsPaused)
|
|
{
|
|
AddLog(LogLevel.Debug, $"Quest {questId} accepted but execution not running");
|
|
return;
|
|
}
|
|
QuestProfile profile = config.GetActiveProfile();
|
|
if (profile == null)
|
|
{
|
|
AddLog(LogLevel.Warning, "No active profile found");
|
|
return;
|
|
}
|
|
QuestConfig questConfig = profile.Quests.FirstOrDefault((QuestConfig q) => q.QuestId == questId && q.TriggerType == TriggerType.OnAccept);
|
|
if (questConfig != null)
|
|
{
|
|
AddLog(LogLevel.Success, $"Quest accepted trigger matched: {questName} (ID: {questId})");
|
|
ExecuteSequence(questConfig);
|
|
}
|
|
else
|
|
{
|
|
AddLog(LogLevel.Info, $"Quest {questId} ({questName}) accepted but no OnAccept trigger configured");
|
|
AddLog(LogLevel.Info, "Add this quest to your profile with TriggerType=OnAccept to auto-execute");
|
|
}
|
|
}
|
|
|
|
private void OnQuestCompleted(uint questId, string questName)
|
|
{
|
|
if (!IsRunning || IsPaused)
|
|
{
|
|
return;
|
|
}
|
|
QuestProfile profile = config.GetActiveProfile();
|
|
if (profile != null)
|
|
{
|
|
QuestConfig questConfig = profile.Quests.FirstOrDefault((QuestConfig q) => q.QuestId == questId && q.TriggerType == TriggerType.OnComplete);
|
|
if (questConfig != null)
|
|
{
|
|
AddLog(LogLevel.Success, "Quest completed trigger matched: " + questName);
|
|
ExecuteSequence(questConfig);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void ExecuteSequence(QuestConfig questConfig)
|
|
{
|
|
CurrentState.CurrentQuestId = questConfig.QuestId;
|
|
CurrentState.CurrentQuestName = questConfig.QuestName;
|
|
CurrentState.CurrentSequence = questConfig.SequenceAfterQuest.Value;
|
|
CurrentState.Status = ExecutionStatus.Running;
|
|
NotifyStateChanged();
|
|
if (config.EnableDryRun)
|
|
{
|
|
AddLog(LogLevel.Debug, "[DRY RUN] Would execute sequence: " + questConfig.SequenceAfterQuest.Value);
|
|
await Task.Delay(2000);
|
|
OnSequenceComplete(questConfig);
|
|
return;
|
|
}
|
|
switch (questConfig.SequenceAfterQuest.Type)
|
|
{
|
|
case SequenceType.QuestionableProfile:
|
|
await ExecuteQuestionableProfile(questConfig);
|
|
break;
|
|
case SequenceType.InternalAction:
|
|
await ExecuteInternalAction(questConfig);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task ExecuteQuestionableProfile(QuestConfig questConfig)
|
|
{
|
|
AddLog(LogLevel.Info, $"Monitoring Quest {questConfig.QuestId} for completion...");
|
|
AddLog(LogLevel.Info, "Questionable will handle quest progression automatically");
|
|
CurrentState.CurrentSequence = $"Monitoring Quest {questConfig.QuestId}";
|
|
NotifyStateChanged();
|
|
await WaitForQuestCompletion(questConfig.QuestId);
|
|
OnSequenceComplete(questConfig);
|
|
}
|
|
|
|
private async Task WaitForQuestAcceptanceThenNext(QuestConfig questConfig)
|
|
{
|
|
int maxWaitSeconds = 3600;
|
|
int waited = 0;
|
|
AddLog(LogLevel.Info, $"Waiting for quest {questConfig.QuestId} to be accepted...");
|
|
while (waited < maxWaitSeconds && IsRunning)
|
|
{
|
|
await Task.Delay(5000);
|
|
waited += 5;
|
|
if (questDetection.IsQuestAccepted(questConfig.QuestId))
|
|
{
|
|
AddLog(LogLevel.Success, $"Quest {questConfig.QuestId} accepted!");
|
|
OnSequenceComplete(questConfig);
|
|
return;
|
|
}
|
|
if (questDetection.IsQuestCompletedDirect(questConfig.QuestId))
|
|
{
|
|
AddLog(LogLevel.Success, $"Quest {questConfig.QuestId} already completed!");
|
|
OnSequenceComplete(questConfig);
|
|
return;
|
|
}
|
|
if (waited % 60 == 0)
|
|
{
|
|
AddLog(LogLevel.Debug, $"Still waiting for quest {questConfig.QuestId} acceptance... ({waited}s)");
|
|
}
|
|
}
|
|
if (!IsRunning)
|
|
{
|
|
AddLog(LogLevel.Info, "Monitoring stopped - execution not running");
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Warning, $"Quest {questConfig.QuestId} acceptance timeout after {maxWaitSeconds}s");
|
|
OnSequenceFailed(questConfig);
|
|
}
|
|
|
|
private async Task WaitForQuestCompletion(uint questId)
|
|
{
|
|
int maxWaitSeconds = 600;
|
|
int waited = 0;
|
|
while (waited < maxWaitSeconds && IsRunning)
|
|
{
|
|
await Task.Delay(5000);
|
|
waited += 5;
|
|
if (questDetection.IsQuestCompletedDirect(questId))
|
|
{
|
|
AddLog(LogLevel.Success, $"Quest {questId} completed!");
|
|
return;
|
|
}
|
|
if (waited % 30 == 0)
|
|
{
|
|
AddLog(LogLevel.Debug, $"Still waiting for quest {questId}... ({waited}s)");
|
|
}
|
|
}
|
|
if (!IsRunning)
|
|
{
|
|
AddLog(LogLevel.Info, "Monitoring stopped - execution not running");
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Warning, $"Quest {questId} completion timeout after {maxWaitSeconds}s");
|
|
}
|
|
|
|
private async Task WaitForQuestionableCompletion()
|
|
{
|
|
AddLog(LogLevel.Warning, "Waiting for Questionable to complete...");
|
|
while (questionableIPC.IsRunning())
|
|
{
|
|
await Task.Delay(1000);
|
|
}
|
|
AddLog(LogLevel.Success, "Questionable completed");
|
|
}
|
|
|
|
private async Task ExecuteInternalAction(QuestConfig questConfig)
|
|
{
|
|
AddLog(LogLevel.Warning, "Internal action not yet implemented: " + questConfig.SequenceAfterQuest.Value);
|
|
await Task.Delay(1000);
|
|
OnSequenceComplete(questConfig);
|
|
}
|
|
|
|
private void OnSequenceComplete(QuestConfig questConfig)
|
|
{
|
|
AddLog(LogLevel.Success, "Sequence completed: " + questConfig.SequenceAfterQuest.Value);
|
|
CurrentState.Status = ExecutionStatus.Complete;
|
|
CurrentState.Progress = 100;
|
|
NotifyStateChanged();
|
|
if (questConfig.NextCharacter == "auto_next")
|
|
{
|
|
SwitchToNextCharacter();
|
|
}
|
|
else if (!string.IsNullOrEmpty(questConfig.NextCharacter))
|
|
{
|
|
SwitchToCharacter(questConfig.NextCharacter);
|
|
}
|
|
}
|
|
|
|
private void OnSequenceFailed(QuestConfig questConfig)
|
|
{
|
|
AddLog(LogLevel.Error, "Sequence failed: " + questConfig.SequenceAfterQuest.Value);
|
|
CurrentState.Status = ExecutionStatus.Failed;
|
|
NotifyStateChanged();
|
|
if (!string.IsNullOrEmpty(CurrentState.CurrentCharacter))
|
|
{
|
|
failedCharacters.Add(CurrentState.CurrentCharacter);
|
|
}
|
|
SwitchToNextCharacter();
|
|
}
|
|
|
|
private async Task SwitchToCharacter(string characterName)
|
|
{
|
|
if (config.EnableDryRun)
|
|
{
|
|
AddLog(LogLevel.Debug, "[DRY RUN] Would switch to character: " + characterName);
|
|
CurrentState.CurrentCharacter = characterName;
|
|
NotifyStateChanged();
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Info, "Switching to character: " + characterName);
|
|
if (questionableIPC.IsRunning())
|
|
{
|
|
questionableIPC.Stop();
|
|
}
|
|
string originalChar = autoRetainerIPC.GetCurrentCharacter();
|
|
AddLog(LogLevel.Debug, "Current character before switch: " + (originalChar ?? "null"));
|
|
if (!string.IsNullOrEmpty(originalChar) && originalChar == characterName)
|
|
{
|
|
AddLog(LogLevel.Info, "Already on character: " + characterName);
|
|
AddLog(LogLevel.Info, "Refreshing quest cache...");
|
|
questDetection.RefreshQuestCache();
|
|
CurrentState.CurrentCharacter = characterName;
|
|
NotifyStateChanged();
|
|
await Task.Delay(2000);
|
|
ExecuteQuestsForCurrentCharacter();
|
|
return;
|
|
}
|
|
if (string.IsNullOrEmpty(originalChar))
|
|
{
|
|
AddLog(LogLevel.Warning, "Could not get current character - might be in transition");
|
|
}
|
|
AddLog(LogLevel.Info, "[AutoRetainerIPC] Relog request sent for: " + characterName);
|
|
if (autoRetainerIPC.SwitchCharacter(characterName))
|
|
{
|
|
waitingForRelog = true;
|
|
targetRelogCharacter = characterName;
|
|
relogStartTime = DateTime.Now;
|
|
wasLoggedOutDuringRelog = false;
|
|
AddLog(LogLevel.Info, "Relog command sent, monitoring via Framework.Update...");
|
|
}
|
|
else
|
|
{
|
|
AddLog(LogLevel.Error, "Failed to send relog request for character: " + characterName);
|
|
failedCharacters.Add(characterName);
|
|
SwitchToNextCharacter();
|
|
}
|
|
}
|
|
|
|
private void ExecuteQuestsForCurrentCharacter()
|
|
{
|
|
AddLog(LogLevel.Info, "=== ExecuteQuestsForCurrentCharacter called ===");
|
|
QuestProfile profile = config.GetActiveProfile();
|
|
if (profile == null)
|
|
{
|
|
AddLog(LogLevel.Error, "No active profile");
|
|
SwitchToNextCharacter();
|
|
return;
|
|
}
|
|
string currentChar = CurrentState.CurrentCharacter;
|
|
if (string.IsNullOrEmpty(currentChar))
|
|
{
|
|
AddLog(LogLevel.Warning, "No current character set");
|
|
SwitchToNextCharacter();
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Info, "Current character: " + currentChar);
|
|
AddLog(LogLevel.Info, $"Total quests in profile: {profile.Quests.Count}");
|
|
List<QuestConfig> characterQuests = (from q in profile.Quests
|
|
where string.IsNullOrEmpty(q.AssignedCharacter) || q.AssignedCharacter == currentChar
|
|
orderby q.QuestId
|
|
select q).ToList();
|
|
if (characterQuests.Count == 0)
|
|
{
|
|
AddLog(LogLevel.Warning, "No quests configured for " + currentChar);
|
|
AddLog(LogLevel.Info, "Please add quests to your profile using /qstcomp");
|
|
completedCharacters.Add(currentChar);
|
|
SwitchToNextCharacter();
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Info, $"Found {characterQuests.Count} quests for {currentChar}");
|
|
foreach (QuestConfig quest in characterQuests)
|
|
{
|
|
AddLog(LogLevel.Debug, $"Checking quest {quest.QuestId}: {quest.QuestName}");
|
|
bool isCompleted = questDetection.IsQuestCompletedDirect(quest.QuestId);
|
|
AddLog(LogLevel.Debug, $"Quest {quest.QuestId} completed status: {isCompleted}");
|
|
if (!isCompleted)
|
|
{
|
|
AddLog(LogLevel.Success, $"Starting quest {quest.QuestId}: {quest.QuestName}");
|
|
CurrentState.CurrentQuestId = quest.QuestId;
|
|
CurrentState.CurrentQuestName = quest.QuestName;
|
|
CurrentState.CurrentSequence = $"Quest {quest.QuestId}";
|
|
NotifyStateChanged();
|
|
if (quest.TriggerType == TriggerType.OnAccept)
|
|
{
|
|
AddLog(LogLevel.Info, $"Quest {quest.QuestId} has OnAccept trigger - waiting for acceptance");
|
|
WaitForQuestAcceptanceThenNext(quest);
|
|
}
|
|
else
|
|
{
|
|
AddLog(LogLevel.Info, $"Quest {quest.QuestId} has OnComplete trigger - monitoring for completion");
|
|
ExecuteSequence(quest);
|
|
}
|
|
return;
|
|
}
|
|
AddLog(LogLevel.Info, $"Quest {quest.QuestId} already completed, skipping");
|
|
}
|
|
AddLog(LogLevel.Success, "All quests completed for " + currentChar + "!");
|
|
completedCharacters.Add(currentChar);
|
|
SwitchToNextCharacter();
|
|
}
|
|
|
|
public void SetSafeWaitService(CharacterSafeWaitService service)
|
|
{
|
|
safeWaitService = service;
|
|
}
|
|
|
|
public void SetPreCheckService(QuestPreCheckService service)
|
|
{
|
|
preCheckService = service;
|
|
}
|
|
|
|
public void SetDCTravelService(DCTravelService service)
|
|
{
|
|
dcTravelService = service;
|
|
}
|
|
|
|
private void SwitchToNextCharacter()
|
|
{
|
|
QuestProfile profile = config.GetActiveProfile();
|
|
if (profile == null || profile.Characters.Count == 0)
|
|
{
|
|
AddLog(LogLevel.Error, "No profile or characters available");
|
|
Stop();
|
|
return;
|
|
}
|
|
if (config.EnableQuestPreCheck && preCheckService != null)
|
|
{
|
|
AddLog(LogLevel.Info, "Logging completed quests before logout...");
|
|
preCheckService.LogCompletedQuestsBeforeLogout();
|
|
}
|
|
if (dcTravelService != null && dcTravelService.IsDCTravelCompleted())
|
|
{
|
|
if (config.ReturnToHomeworldOnStopQuest)
|
|
{
|
|
AddLog(LogLevel.Info, "[DCTravel] Returning to homeworld before character switch...");
|
|
dcTravelService.ReturnToHomeworld();
|
|
Thread.Sleep(2000);
|
|
AddLog(LogLevel.Info, "[DCTravel] Returned to homeworld");
|
|
}
|
|
else
|
|
{
|
|
AddLog(LogLevel.Info, "[DCTravel] Skipping return to homeworld (setting disabled)");
|
|
}
|
|
}
|
|
if (config.EnableSafeWaitBeforeCharacterSwitch && safeWaitService != null)
|
|
{
|
|
AddLog(LogLevel.Info, "[SafeWait] Stabilizing character before switch...");
|
|
safeWaitService.PerformSafeWait();
|
|
AddLog(LogLevel.Info, "[SafeWait] Stabilization complete");
|
|
}
|
|
if (!string.IsNullOrEmpty(CurrentState.CurrentCharacter))
|
|
{
|
|
completedCharacters.Add(CurrentState.CurrentCharacter);
|
|
}
|
|
int attempts = 0;
|
|
while (attempts < profile.Characters.Count)
|
|
{
|
|
currentCharacterIndex = (currentCharacterIndex + 1) % profile.Characters.Count;
|
|
string nextChar = profile.Characters[currentCharacterIndex];
|
|
if (config.EnableQuestPreCheck && preCheckService != null)
|
|
{
|
|
List<StopPoint> stopPoints = config.StopPoints;
|
|
if (stopPoints != null && stopPoints.Count > 0)
|
|
{
|
|
uint firstStopQuest = stopPoints[0].QuestId;
|
|
if (preCheckService.ShouldSkipCharacter(nextChar, firstStopQuest))
|
|
{
|
|
AddLog(LogLevel.Info, "[PreCheck] Skipping " + nextChar + " - quest already completed");
|
|
completedCharacters.Add(nextChar);
|
|
attempts++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (!completedCharacters.Contains(nextChar) && !failedCharacters.Contains(nextChar))
|
|
{
|
|
SwitchToCharacter(nextChar);
|
|
return;
|
|
}
|
|
attempts++;
|
|
}
|
|
AddLog(LogLevel.Success, "All characters completed!");
|
|
Stop();
|
|
}
|
|
|
|
private void AddLog(LogLevel level, string message)
|
|
{
|
|
LogEntry entry = new LogEntry
|
|
{
|
|
Level = level,
|
|
Message = message,
|
|
Timestamp = DateTime.Now
|
|
};
|
|
Logs.Add(entry);
|
|
while (Logs.Count > config.MaxLogEntries)
|
|
{
|
|
Logs.RemoveAt(0);
|
|
}
|
|
log.Information("[ExecutionService] " + message);
|
|
this.LogAdded?.Invoke(entry);
|
|
}
|
|
|
|
private void NotifyStateChanged()
|
|
{
|
|
CurrentState.LastUpdate = DateTime.Now;
|
|
this.StateChanged?.Invoke(CurrentState);
|
|
}
|
|
|
|
private unsafe bool IsNamePlateReady()
|
|
{
|
|
try
|
|
{
|
|
AtkUnitBasePtr namePlatePtr = gameGui.GetAddonByName("NamePlate");
|
|
if (namePlatePtr == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
AddonNamePlate* namePlate = (AddonNamePlate*)namePlatePtr.Address;
|
|
if (namePlate != null && namePlate->AtkUnitBase.IsVisible && namePlate->AtkUnitBase.IsReady)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
questDetection.QuestAccepted -= OnQuestAccepted;
|
|
questDetection.QuestCompleted -= OnQuestCompleted;
|
|
framework.Update -= OnFrameworkUpdate;
|
|
Stop();
|
|
AddLog(LogLevel.Info, "Execution service disposed");
|
|
}
|
|
}
|