262 lines
7.1 KiB
C#
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");
|
|
}
|
|
}
|