qstcompanion v1.0.1
This commit is contained in:
parent
3e10cbbbf2
commit
44c67ab71b
79 changed files with 21148 additions and 0 deletions
|
|
@ -0,0 +1,655 @@
|
|||
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())
|
||||
{
|
||||
AddLog(LogLevel.Info, "[DCTravel] Returning to homeworld before character switch...");
|
||||
dcTravelService.ReturnToHomeworld();
|
||||
Thread.Sleep(2000);
|
||||
AddLog(LogLevel.Info, "[DCTravel] Returned to homeworld");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue