using System; using System.Threading; using System.Threading.Tasks; using Dalamud.Plugin.Services; namespace QuestionableCompanion.Services; public class DCTravelService : IDisposable { private readonly IPluginLog log; private readonly Configuration config; private readonly IClientState clientState; private readonly LifestreamIPC lifestreamIPC; private readonly QuestionableIPC questionableIPC; private readonly CharacterSafeWaitService safeWaitService; private readonly ICommandManager commandManager; private readonly IFramework framework; private bool dcTravelCompleted; private bool dcTravelInProgress; public DCTravelService(IPluginLog log, Configuration config, LifestreamIPC lifestreamIPC, QuestionableIPC questionableIPC, CharacterSafeWaitService safeWaitService, IClientState clientState, ICommandManager commandManager, IFramework framework) { this.log = log; this.config = config; this.lifestreamIPC = lifestreamIPC; this.questionableIPC = questionableIPC; this.safeWaitService = safeWaitService; this.clientState = clientState; this.commandManager = commandManager; this.framework = framework; } public bool ShouldPerformDCTravel() { log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === DC TRAVEL CHECK START ==="); log.Information("[DCTravel] ========================================"); log.Information($"[DCTravel] Config.EnableDCTravel: {config.EnableDCTravel}"); log.Information("[DCTravel] Config.DCTravelWorld: '" + config.DCTravelWorld + "'"); log.Information($"[DCTravel] State.dcTravelCompleted: {dcTravelCompleted}"); log.Information($"[DCTravel] State.dcTravelInProgress: {dcTravelInProgress}"); if (dcTravelCompleted) { log.Warning("[DCTravel] SKIP: Already completed for this character"); return false; } if (dcTravelInProgress) { log.Warning("[DCTravel] SKIP: Travel already in progress"); return false; } if (!config.EnableDCTravel) { log.Warning("[DCTravel] SKIP: DC Travel is DISABLED in config"); return false; } if (string.IsNullOrEmpty(config.DCTravelWorld)) { log.Warning("[DCTravel] SKIP: No target world configured"); return false; } if (clientState.LocalPlayer == null) { log.Error("[DCTravel] SKIP: LocalPlayer is NULL"); return false; } string currentWorld = clientState.LocalPlayer.CurrentWorld.Value.Name.ToString(); log.Information("[DCTravel] Current World: '" + currentWorld + "'"); log.Information("[DCTravel] Target World: '" + config.DCTravelWorld + "'"); if (currentWorld.Equals(config.DCTravelWorld, StringComparison.OrdinalIgnoreCase)) { log.Warning("[DCTravel] SKIP: Already on target world '" + config.DCTravelWorld + "'"); return false; } log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] DC TRAVEL WILL BE PERFORMED!"); log.Information("[DCTravel] ========================================"); return true; } public async Task PerformDCTravel() { if (dcTravelInProgress) { log.Warning("[DCTravel] DC Travel already in progress"); return false; } if (string.IsNullOrEmpty(config.DCTravelWorld)) { log.Error("[DCTravel] No target world configured"); return false; } log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === CHECKING LIFESTREAM AVAILABILITY ==="); log.Information("[DCTravel] ========================================"); log.Information($"[DCTravel] lifestreamIPC.IsAvailable (cached): {lifestreamIPC.IsAvailable}"); bool isAvailable = lifestreamIPC.ForceCheckAvailability(); log.Information($"[DCTravel] lifestreamIPC.ForceCheckAvailability() result: {isAvailable}"); if (!isAvailable) { log.Error("[DCTravel] ========================================"); log.Error("[DCTravel] ======= LIFESTREAM NOT AVAILABLE! ======"); log.Error("[DCTravel] ========================================"); log.Error("[DCTravel] Possible reasons:"); log.Error("[DCTravel] 1. Lifestream plugin is not installed"); log.Error("[DCTravel] 2. Lifestream plugin is not enabled"); log.Error("[DCTravel] 3. Lifestream plugin failed to load"); log.Error("[DCTravel] 4. IPC communication error"); log.Error("[DCTravel] ========================================"); return false; } log.Information("[DCTravel] Lifestream is available!"); dcTravelInProgress = true; try { log.Information("[DCTravel] === INITIATING DATA CENTER TRAVEL ==="); log.Information("[DCTravel] Target World: " + config.DCTravelWorld); log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === STOPPING QUESTIONABLE ==="); log.Information("[DCTravel] ========================================"); log.Information($"[DCTravel] Questionable IsRunning: {questionableIPC.IsRunning()}"); try { framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst stop"); Thread.Sleep(1000); }); log.Information("[DCTravel] /qst stop command sent on Framework Thread"); log.Information($"[DCTravel] Questionable IsRunning after stop: {questionableIPC.IsRunning()}"); } catch (Exception ex) { log.Error("[DCTravel] Error stopping Questionable: " + ex.Message); } log.Information("[DCTravel] Initiating travel to " + config.DCTravelWorld + "..."); log.Information("[DCTravel] Checking Lifestream status before travel..."); log.Information($"[DCTravel] Lifestream.IsAvailable: {lifestreamIPC.IsAvailable}"); log.Information($"[DCTravel] Lifestream.IsBusy(): {lifestreamIPC.IsBusy()}"); if (lifestreamIPC.IsBusy()) { log.Error("[DCTravel] Lifestream is BUSY! Cannot start travel!"); framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }).Wait(); log.Information("[DCTravel] /qst start command sent on Framework Thread (recovery)"); dcTravelInProgress = false; return false; } log.Information("[DCTravel] Sending /li " + config.DCTravelWorld + " command on Framework Thread..."); bool commandSuccess = false; try { framework.RunOnFrameworkThread(delegate { try { commandManager.ProcessCommand("/li " + config.DCTravelWorld); commandSuccess = true; log.Information("[DCTravel] /li " + config.DCTravelWorld + " command executed on Framework Thread"); } catch (Exception ex5) { log.Error("[DCTravel] Failed to execute /li command: " + ex5.Message); commandSuccess = false; } }).Wait(); if (!commandSuccess) { log.Error("[DCTravel] Failed to send /li command"); framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }).Wait(); log.Information("[DCTravel] /qst start command sent (recovery)"); dcTravelInProgress = false; return false; } Thread.Sleep(1000); bool isBusy = lifestreamIPC.IsBusy(); log.Information($"[DCTravel] Lifestream.IsBusy() after command: {isBusy}"); if (!isBusy) { log.Warning("[DCTravel] Lifestream did not become busy after command!"); log.Warning("[DCTravel] Travel may not have started - check Lifestream manually"); } } catch (Exception ex2) { log.Error("[DCTravel] Error sending /li command: " + ex2.Message); framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }).Wait(); log.Information("[DCTravel] /qst start command sent (recovery)"); dcTravelInProgress = false; return false; } log.Information("[DCTravel] Waiting for travel completion..."); if (!(await WaitForTravelCompletion(120))) { log.Warning("[DCTravel] Travel timeout - proceeding anyway"); } log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === RESUMING QUESTIONABLE ==="); log.Information("[DCTravel] ========================================"); try { framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }).Wait(); log.Information("[DCTravel] /qst start command sent on Framework Thread"); Thread.Sleep(1000); log.Information($"[DCTravel] Questionable IsRunning after start: {questionableIPC.IsRunning()}"); } catch (Exception ex3) { log.Error("[DCTravel] Error starting Questionable: " + ex3.Message); } dcTravelCompleted = true; dcTravelInProgress = false; log.Information("[DCTravel] === DATA CENTER TRAVEL COMPLETE ==="); return true; } catch (Exception ex4) { log.Error("[DCTravel] Error during DC travel: " + ex4.Message); try { framework.RunOnFrameworkThread(delegate { commandManager.ProcessCommand("/qst start"); }).Wait(); log.Information("[DCTravel] /qst start command sent on Framework Thread (error recovery)"); } catch { } dcTravelInProgress = false; return false; } } private async Task WaitForTravelCompletion(int timeoutSeconds) { DateTime startTime = DateTime.Now; TimeSpan timeout = TimeSpan.FromSeconds(timeoutSeconds); log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === WAITING FOR TRAVEL TO START ==="); log.Information("[DCTravel] ========================================"); DateTime phase1Start = DateTime.Now; TimeSpan phase1Timeout = TimeSpan.FromSeconds(30L); bool travelStarted = false; log.Information("[DCTravel] Waiting 5 seconds for Lifestream to queue travel tasks..."); await Task.Delay(5000); while (DateTime.Now - phase1Start < phase1Timeout) { try { if (lifestreamIPC.IsBusy()) { log.Information("[DCTravel] Lifestream travel has STARTED!"); travelStarted = true; break; } double elapsed = (DateTime.Now - phase1Start).TotalSeconds; if (elapsed % 5.0 < 0.5) { log.Information($"[DCTravel] Waiting for travel to start... ({elapsed:F1}s)"); } } catch (Exception ex) { log.Debug("[DCTravel] Error checking Lifestream status: " + ex.Message); } await Task.Delay(1000); } if (!travelStarted) { log.Error("[DCTravel] ❌ Travel did not start within 30 seconds!"); return false; } log.Information("[DCTravel] ========================================"); log.Information("[DCTravel] === WAITING FOR TRAVEL TO COMPLETE ==="); log.Information("[DCTravel] ========================================"); while (DateTime.Now - startTime < timeout) { try { if (!lifestreamIPC.IsBusy()) { log.Information("[DCTravel] Lifestream is no longer busy!"); log.Information("[DCTravel] Waiting 30 seconds for character to stabilize..."); await Task.Delay(30000); log.Information("[DCTravel] Travel complete!"); return true; } double elapsed2 = (DateTime.Now - startTime).TotalSeconds; if (elapsed2 % 10.0 < 0.5) { log.Information($"[DCTravel] Lifestream is busy (traveling)... ({elapsed2:F0}s)"); } } catch (Exception ex2) { log.Debug("[DCTravel] Error checking Lifestream status: " + ex2.Message); } await Task.Delay(1000); } log.Warning($"[DCTravel] Travel timeout after {timeoutSeconds}s"); return false; } public async Task ReturnToHomeworld() { if (!lifestreamIPC.IsAvailable) { log.Warning("[DCTravel] Lifestream not available for homeworld return"); return false; } if (clientState.LocalPlayer == null) { return false; } string homeWorld = clientState.LocalPlayer.HomeWorld.Value.Name.ToString(); string currentWorld = clientState.LocalPlayer.CurrentWorld.Value.Name.ToString(); if (homeWorld.Equals(currentWorld, StringComparison.OrdinalIgnoreCase)) { log.Information("[DCTravel] Already on homeworld"); return true; } log.Information("[DCTravel] Returning to homeworld: " + homeWorld); bool success = lifestreamIPC.ChangeWorld(homeWorld); if (success) { await Task.Delay(2000); log.Information("[DCTravel] Homeworld return initiated"); } return success; } public void ResetDCTravelState() { dcTravelCompleted = false; dcTravelInProgress = false; log.Information("[DCTravel] DC Travel state reset"); } public bool IsDCTravelCompleted() { return dcTravelCompleted; } public bool IsDCTravelInProgress() { return dcTravelInProgress; } public void Dispose() { log.Information("[DCTravel] Service disposed"); } }