qstbak/QuestionableCompanion/QuestionableCompanion.Services/AutoRetainerIPC.cs
2025-12-04 04:40:50 +10:00

511 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
using Newtonsoft.Json.Linq;
namespace QuestionableCompanion.Services;
public class AutoRetainerIPC : IDisposable
{
private readonly IDalamudPluginInterface pluginInterface;
private readonly IPluginLog log;
private readonly IClientState clientState;
private readonly ICommandManager commandManager;
private readonly IFramework framework;
private ICallGateSubscriber<List<ulong>>? getRegisteredCIDsSubscriber;
private ICallGateSubscriber<ulong, object>? getOfflineCharacterDataSubscriber;
private ICallGateProvider<string, object>? relogProvider;
private ICallGateSubscriber<bool>? getMultiModeEnabledSubscriber;
private ICallGateProvider<bool, object>? setMultiModeEnabledProvider;
private Dictionary<ulong, string> characterCache = new Dictionary<ulong, string>();
private HashSet<ulong> unknownCIDs = new HashSet<ulong>();
private bool subscribersInitialized;
private DateTime lastAvailabilityCheck = DateTime.MinValue;
private const int AvailabilityCheckCooldownSeconds = 5;
public bool IsAvailable { get; private set; }
public AutoRetainerIPC(IDalamudPluginInterface pluginInterface, IPluginLog log, IClientState clientState, ICommandManager commandManager, IFramework framework)
{
this.pluginInterface = pluginInterface;
this.log = log;
this.clientState = clientState;
this.commandManager = commandManager;
this.framework = framework;
InitializeIPC();
}
public void ClearCache()
{
characterCache.Clear();
unknownCIDs.Clear();
log.Information("[AutoRetainerIPC] Cache cleared");
}
private void InitializeIPC()
{
try
{
getRegisteredCIDsSubscriber = null;
getOfflineCharacterDataSubscriber = null;
relogProvider = null;
getMultiModeEnabledSubscriber = null;
setMultiModeEnabledProvider = null;
IsAvailable = false;
getRegisteredCIDsSubscriber = pluginInterface.GetIpcSubscriber<List<ulong>>("AutoRetainer.GetRegisteredCIDs");
getOfflineCharacterDataSubscriber = pluginInterface.GetIpcSubscriber<ulong, object>("AutoRetainer.GetOfflineCharacterData");
try
{
relogProvider = pluginInterface.GetIpcProvider<string, object>("AutoRetainer.Relog");
log.Debug("[AutoRetainerIPC] Relog IPC provider initialized");
}
catch (Exception ex)
{
log.Debug("[AutoRetainerIPC] Failed to initialize Relog provider: " + ex.Message);
}
try
{
getMultiModeEnabledSubscriber = pluginInterface.GetIpcSubscriber<bool>("AutoRetainer.GetMultiModeEnabled");
setMultiModeEnabledProvider = pluginInterface.GetIpcProvider<bool, object>("AutoRetainer.SetMultiModeEnabled");
log.Debug("[AutoRetainerIPC] Multi-Mode IPC initialized");
}
catch (Exception ex2)
{
log.Debug("[AutoRetainerIPC] Failed to initialize Multi-Mode IPC: " + ex2.Message);
}
subscribersInitialized = true;
log.Debug("[AutoRetainerIPC] IPC subscribers initialized (lazy-loading enabled)");
}
catch (Exception ex3)
{
IsAvailable = false;
subscribersInitialized = false;
log.Error("[AutoRetainerIPC] Failed to initialize subscribers: " + ex3.Message);
}
}
private bool TryEnsureAvailable()
{
if (IsAvailable)
{
return true;
}
if (!subscribersInitialized)
{
return false;
}
DateTime now = DateTime.Now;
if ((now - lastAvailabilityCheck).TotalSeconds < 5.0)
{
return false;
}
lastAvailabilityCheck = now;
try
{
if (getRegisteredCIDsSubscriber == null)
{
return false;
}
List<ulong> testCids = getRegisteredCIDsSubscriber.InvokeFunc();
if (!IsAvailable)
{
IsAvailable = true;
log.Information($"[AutoRetainerIPC] ✅ AutoRetainer is now available ({testCids?.Count ?? 0} characters)");
}
return true;
}
catch (Exception ex)
{
log.Debug("[AutoRetainerIPC] AutoRetainer not yet available: " + ex.Message);
IsAvailable = false;
return false;
}
}
public bool TryReinitialize()
{
log.Information("[AutoRetainerIPC] Manual IPC reinitialization requested");
lastAvailabilityCheck = DateTime.MinValue;
bool num = TryEnsureAvailable();
if (num)
{
log.Information("[AutoRetainerIPC] IPC reinitialization successful");
return num;
}
log.Warning("[AutoRetainerIPC] IPC still unavailable after reinitialization attempt");
return num;
}
public List<string> GetRegisteredCharacters()
{
log.Debug("[AutoRetainerIPC] GetRegisteredCharacters called");
TryEnsureAvailable();
if (!IsAvailable || getRegisteredCIDsSubscriber == null)
{
log.Warning("[AutoRetainerIPC] Cannot get characters - IPC not available");
log.Warning($"[AutoRetainerIPC] IsAvailable: {IsAvailable}, Subscriber: {getRegisteredCIDsSubscriber != null}");
return new List<string>();
}
try
{
List<ulong> cids = getRegisteredCIDsSubscriber.InvokeFunc();
if (cids == null || cids.Count == 0)
{
log.Warning("[AutoRetainerIPC] No CIDs returned from AutoRetainer");
return new List<string>();
}
List<string> characters = new List<string>();
foreach (ulong cid in cids)
{
string charName = GetCharacterNameFromCID(cid);
if (!string.IsNullOrEmpty(charName))
{
characters.Add(charName);
continue;
}
log.Debug($"[AutoRetainerIPC] Could not resolve name for CID: {cid}");
}
if (characters.Count == 0)
{
log.Warning("[AutoRetainerIPC] No character names could be resolved from CIDs");
}
return characters;
}
catch (Exception ex)
{
log.Error("[AutoRetainerIPC] GetRegisteredCharacters failed: " + ex.Message);
log.Error("[AutoRetainerIPC] Stack trace: " + ex.StackTrace);
return new List<string>();
}
}
private string GetCharacterNameFromCID(ulong cid)
{
if (characterCache.TryGetValue(cid, out string cachedName))
{
if (cachedName.Contains("@"))
{
return cachedName;
}
log.Debug($"[AutoRetainerIPC] Removing invalid cache entry for CID {cid}: '{cachedName}'");
characterCache.Remove(cid);
}
if (unknownCIDs.Contains(cid))
{
return $"Unknown (CID: {cid})";
}
if (getOfflineCharacterDataSubscriber == null)
{
log.Debug("[AutoRetainerIPC] OfflineCharacterData subscriber is null");
return string.Empty;
}
try
{
object data = getOfflineCharacterDataSubscriber.InvokeFunc(cid);
if (data == null)
{
if (!unknownCIDs.Contains(cid))
{
log.Warning($"[AutoRetainerIPC] No data returned for CID {cid}");
unknownCIDs.Add(cid);
}
return $"Unknown (CID: {cid})";
}
string resolvedName = null;
FieldInfo nameField = data.GetType().GetField("Name");
FieldInfo worldField = data.GetType().GetField("World");
if (nameField != null && worldField != null)
{
string name = nameField.GetValue(data)?.ToString();
string world = worldField.GetValue(data)?.ToString();
log.Warning($"[AutoRetainerIPC] Field values for CID {cid} - Name: '{name}', World: '{world}'");
if (!string.IsNullOrEmpty(name) && name != "Unknown")
{
if (string.IsNullOrEmpty(world) && clientState.IsLoggedIn && clientState.LocalPlayer != null && clientState.LocalPlayer.Name.ToString() == name)
{
world = clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
log.Information("[AutoRetainerIPC] Resolved world from ClientState for " + name + ": " + world);
}
if (!string.IsNullOrEmpty(world))
{
resolvedName = name + "@" + world;
characterCache[cid] = resolvedName;
return resolvedName;
}
log.Warning($"[AutoRetainerIPC] World is empty for CID {cid}, cannot create full name");
}
else
{
log.Warning($"[AutoRetainerIPC] Name is empty/invalid for CID {cid}");
}
}
PropertyInfo nameProperty = data.GetType().GetProperty("Name");
PropertyInfo worldProperty = data.GetType().GetProperty("World");
if (nameProperty != null && worldProperty != null)
{
string name2 = nameProperty.GetValue(data)?.ToString();
string world2 = worldProperty.GetValue(data)?.ToString();
if (!string.IsNullOrEmpty(name2) && !string.IsNullOrEmpty(world2) && name2 != "Unknown")
{
resolvedName = name2 + "@" + world2;
characterCache[cid] = resolvedName;
return resolvedName;
}
}
if (data is JToken jToken)
{
resolvedName = ParseJTokenCharacterData(jToken, cid);
if (!string.IsNullOrEmpty(resolvedName))
{
characterCache[cid] = resolvedName;
return resolvedName;
}
}
if (!unknownCIDs.Contains(cid))
{
log.Warning($"[AutoRetainerIPC] Could not resolve name for CID {cid}");
LogDataStructure(data, cid);
unknownCIDs.Add(cid);
}
return $"Unknown (CID: {cid})";
}
catch (Exception ex)
{
if (!unknownCIDs.Contains(cid))
{
log.Warning($"[AutoRetainerIPC] Exception resolving CID {cid}: {ex.Message}");
log.Debug("[AutoRetainerIPC] Stack trace: " + ex.StackTrace);
unknownCIDs.Add(cid);
}
return $"Unknown (CID: {cid})";
}
}
private string? ParseJTokenCharacterData(JToken jToken, ulong cid)
{
try
{
JToken nameToken = jToken.SelectToken("Name") ?? jToken.SelectToken("name") ?? jToken.SelectToken("Value.Name");
JToken worldToken = jToken.SelectToken("World") ?? jToken.SelectToken("world") ?? jToken.SelectToken("Value.World");
if (nameToken != null && worldToken != null)
{
string name = nameToken.Value<string>();
string world = worldToken.Value<string>();
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(world))
{
return name + "@" + world;
}
if (!string.IsNullOrEmpty(name))
{
log.Warning($"[AutoRetainerIPC] JSON has Name but World is empty for CID {cid}");
}
}
string[] array = new string[4] { "NameWithWorld", "nameWithWorld", "[\"NameWithWorld\"]", "Value.NameWithWorld" };
foreach (string path in array)
{
if (string.IsNullOrEmpty(path))
{
continue;
}
JToken token = jToken.SelectToken(path);
if (token != null && token.Type == JTokenType.String)
{
string value = token.Value<string>();
if (!string.IsNullOrEmpty(value) && value.Contains("@"))
{
log.Information("[AutoRetainerIPC] Found name via JSON path '" + path + "': " + value);
return value;
}
}
}
}
catch (Exception ex)
{
log.Warning($"[AutoRetainerIPC] Error parsing JToken for CID {cid}: {ex.Message}");
}
return null;
}
private void LogDataStructure(object data, ulong cid)
{
try
{
if (data is JToken jToken)
{
log.Debug($"[AutoRetainerIPC] JSON structure for CID {cid}:");
log.Debug(jToken.ToString());
return;
}
PropertyInfo[] properties = data.GetType().GetProperties();
log.Debug($"[AutoRetainerIPC] Object structure for CID {cid}:");
foreach (PropertyInfo prop in properties.Take(10))
{
try
{
object value = prop.GetValue(data);
log.Debug($" {prop.Name} = {value ?? "(null)"}");
}
catch
{
log.Debug(" " + prop.Name + " = (error reading value)");
}
}
}
catch
{
}
}
public string? GetCurrentCharacter()
{
try
{
string result = null;
framework.RunOnFrameworkThread(delegate
{
try
{
if (!clientState.IsLoggedIn)
{
result = null;
}
else if (clientState.LocalPlayer == null)
{
result = null;
}
else
{
string text = clientState.LocalPlayer.Name.ToString();
string text2 = clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2))
{
result = null;
}
else
{
result = text + "@" + text2;
}
}
}
catch (Exception ex2)
{
log.Debug("[AutoRetainerIPC] GetCurrentCharacter inner failed: " + ex2.Message);
result = null;
}
}).Wait();
return result;
}
catch (Exception ex)
{
log.Debug("[AutoRetainerIPC] GetCurrentCharacter failed: " + ex.Message);
return null;
}
}
public bool SwitchCharacter(string characterNameWithWorld)
{
if (string.IsNullOrEmpty(characterNameWithWorld))
{
log.Warning("[AutoRetainerIPC] Character name is null or empty");
return false;
}
TryEnsureAvailable();
if (!IsAvailable)
{
log.Warning("[AutoRetainerIPC] AutoRetainer not available");
return false;
}
try
{
log.Information("[AutoRetainerIPC] Requesting relog to: " + characterNameWithWorld);
string command = "/ays relog " + characterNameWithWorld;
bool success = false;
framework.RunOnFrameworkThread(delegate
{
try
{
commandManager.ProcessCommand(command);
success = true;
log.Information("[AutoRetainerIPC] Relog command executed: " + command);
}
catch (Exception ex2)
{
log.Error("[AutoRetainerIPC] Failed to execute relog command: " + ex2.Message);
success = false;
}
}).Wait();
return success;
}
catch (Exception ex)
{
log.Error("[AutoRetainerIPC] Failed to switch character: " + ex.Message);
return false;
}
}
public bool GetMultiModeEnabled()
{
TryEnsureAvailable();
if (!IsAvailable || getMultiModeEnabledSubscriber == null)
{
log.Debug("[AutoRetainerIPC] Multi-Mode IPC not available");
return false;
}
try
{
return getMultiModeEnabledSubscriber.InvokeFunc();
}
catch (Exception ex)
{
log.Error("[AutoRetainerIPC] GetMultiModeEnabled failed: " + ex.Message);
return false;
}
}
public bool SetMultiModeEnabled(bool enabled)
{
TryEnsureAvailable();
if (!IsAvailable || setMultiModeEnabledProvider == null)
{
log.Warning("[AutoRetainerIPC] Multi-Mode IPC not available");
return false;
}
try
{
setMultiModeEnabledProvider.SendMessage(enabled);
log.Information($"[AutoRetainerIPC] Multi-Mode set to: {enabled}");
return true;
}
catch (Exception ex)
{
log.Error("[AutoRetainerIPC] SetMultiModeEnabled failed: " + ex.Message);
return false;
}
}
public void Dispose()
{
IsAvailable = false;
characterCache.Clear();
unknownCIDs.Clear();
log.Information("[AutoRetainerIPC] Service disposed");
}
}