qstbak/QuestionableCompanion/QuestionableCompanion.Services/LifestreamIPC.cs
2025-12-04 04:39:08 +10:00

262 lines
7.1 KiB
C#

using System;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
namespace QuestionableCompanion.Services;
public class LifestreamIPC : IDisposable
{
private readonly IPluginLog log;
private readonly IDalamudPluginInterface pluginInterface;
private ICallGateSubscriber<bool>? isBusySubscriber;
private ICallGateSubscriber<string, bool>? changeWorldSubscriber;
private ICallGateSubscriber<uint, bool>? changeWorldByIdSubscriber;
private ICallGateSubscriber<object>? abortSubscriber;
private bool _isAvailable;
private bool _ipcInitialized;
private DateTime lastAvailabilityCheck = DateTime.MinValue;
private const int AvailabilityCheckCooldownSeconds = 5;
private bool hasPerformedInitialCheck;
public bool IsAvailable
{
get
{
return _isAvailable;
}
private set
{
_isAvailable = value;
}
}
public LifestreamIPC(IPluginLog log, IDalamudPluginInterface pluginInterface)
{
this.log = log;
this.pluginInterface = pluginInterface;
}
private void InitializeIPC()
{
if (_ipcInitialized)
{
return;
}
try
{
isBusySubscriber = pluginInterface.GetIpcSubscriber<bool>("Lifestream.IsBusy");
changeWorldSubscriber = pluginInterface.GetIpcSubscriber<string, bool>("Lifestream.ChangeWorld");
changeWorldByIdSubscriber = pluginInterface.GetIpcSubscriber<uint, bool>("Lifestream.ChangeWorldById");
abortSubscriber = pluginInterface.GetIpcSubscriber<object>("Lifestream.Abort");
_ipcInitialized = true;
log.Debug("[LifestreamIPC] IPC subscribers initialized (lazy-loading enabled)");
}
catch (Exception ex)
{
log.Error("[LifestreamIPC] Failed to initialize subscribers: " + ex.Message);
_isAvailable = false;
_ipcInitialized = false;
}
}
private bool TryEnsureAvailable(bool forceCheck = false)
{
if (_isAvailable)
{
return true;
}
if (!_ipcInitialized)
{
InitializeIPC();
}
if (!_ipcInitialized)
{
return false;
}
DateTime now = DateTime.Now;
if (!forceCheck && hasPerformedInitialCheck && (now - lastAvailabilityCheck).TotalSeconds < 5.0)
{
log.Debug($"[LifestreamIPC] Cooldown active - skipping check (last check: {(now - lastAvailabilityCheck).TotalSeconds:F1}s ago)");
return false;
}
if (forceCheck)
{
log.Information("[LifestreamIPC] FORCED availability check requested");
}
lastAvailabilityCheck = now;
hasPerformedInitialCheck = true;
try
{
if (isBusySubscriber == null)
{
log.Debug("[LifestreamIPC] isBusySubscriber is NULL - cannot check availability");
_isAvailable = false;
return false;
}
log.Debug("[LifestreamIPC] Attempting to invoke Lifestream.IsBusy()...");
bool testBusy = isBusySubscriber.InvokeFunc();
if (!_isAvailable)
{
_isAvailable = true;
log.Information($"[LifestreamIPC] Lifestream is now available (Busy: {testBusy})");
}
else
{
log.Debug($"[LifestreamIPC] Lifestream still available (Busy: {testBusy})");
}
return true;
}
catch (Exception ex)
{
if (!hasPerformedInitialCheck)
{
log.Warning("[LifestreamIPC] First availability check FAILED: " + ex.GetType().Name + ": " + ex.Message);
}
else
{
log.Debug("[LifestreamIPC] Lifestream not yet available: " + ex.Message);
}
_isAvailable = false;
return false;
}
}
public bool IsBusy()
{
TryEnsureAvailable();
if (!_isAvailable || isBusySubscriber == null)
{
return false;
}
try
{
return isBusySubscriber.InvokeFunc();
}
catch (Exception ex)
{
log.Error("[LifestreamIPC] Error checking busy status: " + ex.Message);
return false;
}
}
public bool ForceCheckAvailability()
{
log.Information("[LifestreamIPC] ========================================");
log.Information("[LifestreamIPC] === FORCING AVAILABILITY CHECK ===");
log.Information("[LifestreamIPC] ========================================");
bool result = TryEnsureAvailable(forceCheck: true);
log.Information($"[LifestreamIPC] Force check result: {result}");
return result;
}
public bool ChangeWorld(string worldName)
{
TryEnsureAvailable();
log.Information("[LifestreamIPC] ========================================");
log.Information("[LifestreamIPC] === CHANGE WORLD REQUEST ===");
log.Information("[LifestreamIPC] ========================================");
log.Information("[LifestreamIPC] Target World: '" + worldName + "'");
log.Information($"[LifestreamIPC] IsAvailable: {_isAvailable}");
log.Information($"[LifestreamIPC] changeWorldSubscriber != null: {changeWorldSubscriber != null}");
if (!_isAvailable || changeWorldSubscriber == null)
{
log.Error("[LifestreamIPC] CANNOT CHANGE WORLD - Lifestream not available!");
log.Error("[LifestreamIPC] Make sure Lifestream plugin is installed and enabled!");
return false;
}
try
{
log.Information("[LifestreamIPC] Invoking Lifestream.ChangeWorld('" + worldName + "')...");
bool num = changeWorldSubscriber.InvokeFunc(worldName);
if (num)
{
log.Information("[LifestreamIPC] ========================================");
log.Information("[LifestreamIPC] WORLD CHANGE ACCEPTED: " + worldName);
log.Information("[LifestreamIPC] ========================================");
}
else
{
log.Warning("[LifestreamIPC] ========================================");
log.Warning("[LifestreamIPC] WORLD CHANGE REJECTED: " + worldName);
log.Warning("[LifestreamIPC] ========================================");
log.Warning("[LifestreamIPC] Possible reasons:");
log.Warning("[LifestreamIPC] - Lifestream is busy");
log.Warning("[LifestreamIPC] - World name is invalid");
log.Warning("[LifestreamIPC] - Cannot visit this world");
}
return num;
}
catch (Exception ex)
{
log.Error("[LifestreamIPC] ========================================");
log.Error("[LifestreamIPC] ERROR REQUESTING WORLD CHANGE!");
log.Error("[LifestreamIPC] ========================================");
log.Error("[LifestreamIPC] Error: " + ex.Message);
log.Error("[LifestreamIPC] Stack: " + ex.StackTrace);
return false;
}
}
public bool ChangeWorldById(uint worldId)
{
TryEnsureAvailable();
if (!_isAvailable || changeWorldByIdSubscriber == null)
{
log.Warning("[LifestreamIPC] Lifestream not available for world change");
return false;
}
try
{
log.Information($"[LifestreamIPC] Requesting world change to ID: {worldId}");
bool num = changeWorldByIdSubscriber.InvokeFunc(worldId);
if (num)
{
log.Information($"[LifestreamIPC] World change request accepted for ID: {worldId}");
}
else
{
log.Warning($"[LifestreamIPC] World change request rejected for ID: {worldId}");
}
return num;
}
catch (Exception ex)
{
log.Error("[LifestreamIPC] Error requesting world change by ID: " + ex.Message);
return false;
}
}
public void Abort()
{
TryEnsureAvailable();
if (!_isAvailable || abortSubscriber == null)
{
return;
}
try
{
abortSubscriber.InvokeAction();
log.Information("[LifestreamIPC] Abort request sent to Lifestream");
}
catch (Exception ex)
{
log.Error("[LifestreamIPC] Error aborting Lifestream: " + ex.Message);
}
}
public void Dispose()
{
log.Information("[LifestreamIPC] Service disposed");
}
}