1
0
Fork 0
forked from aly/qstbak

qstcompanion v1.0.6

This commit is contained in:
alydev 2025-12-07 10:54:53 +10:00
parent 5e1e1decc5
commit ada27cf05b
30 changed files with 3403 additions and 426 deletions

View file

@ -4,8 +4,12 @@ using System.Linq;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using QuestionableCompanion.Models;
namespace QuestionableCompanion.Services;
@ -33,13 +37,17 @@ public class HelperManager : IDisposable
private readonly MemoryHelper memoryHelper;
private readonly LANHelperClient? lanHelperClient;
private readonly IPartyList partyList;
private bool isInDuty;
private List<(string Name, ushort WorldId)> availableHelpers = new List<(string, ushort)>();
private Dictionary<(string, ushort), bool> helperReadyStatus = new Dictionary<(string, ushort), bool>();
public HelperManager(Configuration configuration, IPluginLog log, ICommandManager commandManager, ICondition condition, IClientState clientState, IFramework framework, PartyInviteService partyInviteService, MultiClientIPC multiClientIPC, CrossProcessIPC crossProcessIPC, PartyInviteAutoAccept partyInviteAutoAccept, MemoryHelper memoryHelper)
public HelperManager(Configuration configuration, IPluginLog log, ICommandManager commandManager, ICondition condition, IClientState clientState, IFramework framework, PartyInviteService partyInviteService, MultiClientIPC multiClientIPC, CrossProcessIPC crossProcessIPC, PartyInviteAutoAccept partyInviteAutoAccept, MemoryHelper memoryHelper, LANHelperClient? lanHelperClient, IPartyList partyList)
{
this.configuration = configuration;
this.log = log;
@ -51,7 +59,9 @@ public class HelperManager : IDisposable
this.multiClientIPC = multiClientIPC;
this.crossProcessIPC = crossProcessIPC;
this.memoryHelper = memoryHelper;
this.lanHelperClient = lanHelperClient;
this.partyInviteAutoAccept = partyInviteAutoAccept;
this.partyList = partyList;
condition.ConditionChange += OnConditionChanged;
multiClientIPC.OnHelperRequested += OnHelperRequested;
multiClientIPC.OnHelperDismissed += OnHelperDismissed;
@ -95,56 +105,236 @@ public class HelperManager : IDisposable
log.Debug("[HelperManager] Not a Quester, skipping helper invites");
return;
}
if (configuration.HelperSelection == HelperSelectionMode.ManualInput)
{
if (string.IsNullOrEmpty(configuration.ManualHelperName))
{
log.Warning("[HelperManager] Manual Input mode selected but no helper name configured!");
return;
}
Task.Run(async delegate
{
log.Information("[HelperManager] Manual Input mode: Inviting " + configuration.ManualHelperName);
string[] parts = configuration.ManualHelperName.Split('@');
if (parts.Length != 2)
{
log.Error("[HelperManager] Invalid manual helper format: " + configuration.ManualHelperName + " (expected: CharacterName@WorldName)");
}
else
{
string helperName = parts[0].Trim();
string worldName = parts[1].Trim();
ushort worldId = 0;
ExcelSheet<World> worldSheet = Plugin.DataManager.GetExcelSheet<World>();
if (worldSheet != null)
{
foreach (World world in worldSheet)
{
if (world.Name.ExtractText().Equals(worldName, StringComparison.OrdinalIgnoreCase))
{
worldId = (ushort)world.RowId;
break;
}
}
}
if (worldId == 0)
{
log.Error("[HelperManager] Could not find world ID for: " + worldName);
}
else
{
log.Information($"[HelperManager] Resolved helper: {helperName}@{worldId} ({worldName})");
bool alreadyInParty = false;
if (partyList != null)
{
foreach (IPartyMember member in partyList)
{
if (member.Name.ToString() == helperName && member.World.RowId == worldId)
{
alreadyInParty = true;
break;
}
}
}
if (alreadyInParty)
{
log.Information("[HelperManager] helper " + helperName + " is ALREADY in party! Skipping disband/invite.");
}
else
{
DisbandParty();
await Task.Delay(500);
log.Information("[HelperManager] Sending direct invite to " + helperName + " (Manual Input - no IPC wait)");
if (partyInviteService.InviteToParty(helperName, worldId))
{
log.Information("[HelperManager] Successfully invited " + helperName);
}
else
{
log.Error("[HelperManager] Failed to invite " + helperName);
}
}
}
}
});
return;
}
log.Information("[HelperManager] Requesting helper announcements...");
RequestHelperAnnouncements();
Task.Run(async delegate
{
await Task.Delay(1000);
if (availableHelpers.Count == 0)
List<(string Name, ushort WorldId)> helpersToInvite = new List<(string, ushort)>();
if (configuration.HelperSelection == HelperSelectionMode.Auto)
{
log.Warning("[HelperManager] No helpers available via IPC!");
log.Warning("[HelperManager] Make sure helper clients are running with 'I'm a High-Level Helper' enabled");
if (availableHelpers.Count == 0)
{
log.Warning("[HelperManager] No helpers available via IPC!");
if (lanHelperClient != null)
{
log.Information("[HelperManager] Checking for LAN helpers...");
LANHelperInfo lanHelper = lanHelperClient.GetFirstAvailableHelper();
if (lanHelper != null)
{
log.Information("[HelperManager] Found LAN helper: " + lanHelper.Name + " at " + lanHelper.IPAddress);
await InviteLANHelper(lanHelper.IPAddress, lanHelper.Name, lanHelper.WorldId);
return;
}
}
log.Warning("[HelperManager] Make sure helper clients are running with 'I'm a High-Level Helper' enabled");
return;
}
helpersToInvite.AddRange(availableHelpers);
log.Information($"[HelperManager] Auto mode: Inviting {helpersToInvite.Count} AUTO-DISCOVERED helper(s)...");
}
else if (configuration.HelperSelection == HelperSelectionMode.Dropdown)
{
if (string.IsNullOrEmpty(configuration.PreferredHelper))
{
log.Warning("[HelperManager] Dropdown mode selected but no helper chosen!");
return;
}
string[] parts = configuration.PreferredHelper.Split('@');
if (parts.Length != 2)
{
log.Error("[HelperManager] Invalid preferred helper format: " + configuration.PreferredHelper);
return;
}
string helperName = parts[0].Trim();
string worldName = parts[1].Trim();
(string, ushort) matchingHelper = availableHelpers.FirstOrDefault<(string, ushort)>(delegate((string Name, ushort WorldId) h)
{
ExcelSheet<World> excelSheet = Plugin.DataManager.GetExcelSheet<World>();
string text2 = "Unknown";
if (excelSheet != null)
{
foreach (World current in excelSheet)
{
if (current.RowId == h.WorldId)
{
text2 = current.Name.ExtractText();
break;
}
}
}
return h.Name == helperName && text2 == worldName;
});
var (text, num) = matchingHelper;
if (text == null && num == 0)
{
log.Warning("[HelperManager] Preferred helper " + configuration.PreferredHelper + " not found in discovered helpers!");
return;
}
helpersToInvite.Add(matchingHelper);
log.Information("[HelperManager] Dropdown mode: Inviting selected helper " + configuration.PreferredHelper);
}
bool allHelpersPresent = false;
if (partyList != null && partyList.Length > 0 && helpersToInvite.Count > 0)
{
int presentCount = 0;
foreach (var (hName, hWorld) in helpersToInvite)
{
foreach (IPartyMember member in partyList)
{
if (member.Name.ToString() == hName && member.World.RowId == hWorld)
{
presentCount++;
break;
}
}
}
if (presentCount >= helpersToInvite.Count)
{
allHelpersPresent = true;
}
}
if (allHelpersPresent)
{
log.Information("[HelperManager] All desired helpers are ALREADY in party! Skipping disband.");
}
else if (partyList != null && partyList.Length > 1)
{
bool anyHelperPresent = false;
foreach (var (hName2, hWorld2) in helpersToInvite)
{
foreach (IPartyMember member2 in partyList)
{
if (member2.Name.ToString() == hName2 && member2.World.RowId == hWorld2)
{
anyHelperPresent = true;
break;
}
}
}
if (anyHelperPresent)
{
log.Information("[HelperManager] Some helpers already in party - NOT disbanding, simply inviting remaining.");
}
else
{
DisbandParty();
await Task.Delay(500);
}
}
else
{
log.Information($"[HelperManager] Inviting {availableHelpers.Count} AUTO-DISCOVERED helper(s)...");
DisbandParty();
await Task.Delay(500);
foreach (var (name, worldId) in availableHelpers)
}
foreach (var (name, worldId) in helpersToInvite)
{
if (string.IsNullOrEmpty(name) || worldId == 0)
{
if (string.IsNullOrEmpty(name) || worldId == 0)
log.Warning($"[HelperManager] Invalid helper: {name}@{worldId}");
}
else
{
log.Information($"[HelperManager] Requesting helper: {name}@{worldId}");
helperReadyStatus[(name, worldId)] = false;
multiClientIPC.RequestHelper(name, worldId);
crossProcessIPC.RequestHelper(name, worldId);
log.Information("[HelperManager] Waiting for " + name + " to be ready...");
DateTime timeout = DateTime.Now.AddSeconds(10.0);
while (!helperReadyStatus.GetValueOrDefault((name, worldId), defaultValue: false) && DateTime.Now < timeout)
{
log.Warning($"[HelperManager] Invalid helper: {name}@{worldId}");
await Task.Delay(100);
}
if (!helperReadyStatus.GetValueOrDefault((name, worldId), defaultValue: false))
{
log.Warning("[HelperManager] Timeout waiting for " + name + " to be ready!");
}
else
{
log.Information($"[HelperManager] Requesting helper: {name}@{worldId}");
helperReadyStatus[(name, worldId)] = false;
multiClientIPC.RequestHelper(name, worldId);
crossProcessIPC.RequestHelper(name, worldId);
log.Information("[HelperManager] Waiting for " + name + " to be ready...");
DateTime timeout = DateTime.Now.AddSeconds(10.0);
while (!helperReadyStatus.GetValueOrDefault((name, worldId), defaultValue: false) && DateTime.Now < timeout)
log.Information("[HelperManager] " + name + " is ready! Sending invite...");
if (partyInviteService.InviteToParty(name, worldId))
{
await Task.Delay(100);
}
if (!helperReadyStatus.GetValueOrDefault((name, worldId), defaultValue: false))
{
log.Warning("[HelperManager] Timeout waiting for " + name + " to be ready!");
log.Information("[HelperManager] Successfully invited " + name);
}
else
{
log.Information("[HelperManager] " + name + " is ready! Sending invite...");
if (partyInviteService.InviteToParty(name, worldId))
{
log.Information("[HelperManager] Successfully invited " + name);
}
else
{
log.Error("[HelperManager] Failed to invite " + name);
}
await Task.Delay(500);
log.Error("[HelperManager] Failed to invite " + name);
}
await Task.Delay(500);
}
}
}
@ -153,7 +343,18 @@ public class HelperManager : IDisposable
public List<(string Name, ushort WorldId)> GetAvailableHelpers()
{
return new List<(string, ushort)>(availableHelpers);
List<(string, ushort)> allHelpers = new List<(string, ushort)>(availableHelpers);
if (lanHelperClient != null)
{
foreach (LANHelperInfo lanHelper in lanHelperClient.DiscoveredHelpers)
{
if (!allHelpers.Any<(string, ushort)>(((string Name, ushort WorldId) h) => h.Name == lanHelper.Name && h.WorldId == lanHelper.WorldId))
{
allHelpers.Add((lanHelper.Name, lanHelper.WorldId));
}
}
}
return allHelpers;
}
private void LeaveParty()
@ -211,7 +412,7 @@ public class HelperManager : IDisposable
private void OnDutyEnter()
{
log.Information("[HelperManager] Entered duty");
log.Debug("[HelperManager] Entered duty");
if (!configuration.IsHighLevelHelper)
{
return;
@ -345,14 +546,34 @@ public class HelperManager : IDisposable
GroupManager.Group* group = groupManager->GetGroup();
if (group != null && group->MemberCount > 0)
{
needsToLeaveParty = true;
log.Information("[HelperManager] Currently in party, notifying quester...");
crossProcessIPC.NotifyHelperInParty(localName, localWorldId);
if (condition[ConditionFlag.BoundByDuty])
bool requesterInParty = false;
if (partyList != null)
{
isInDuty = true;
log.Information("[HelperManager] Currently in duty, notifying quester...");
crossProcessIPC.NotifyHelperInDuty(localName, localWorldId);
foreach (IPartyMember member in partyList)
{
if (member.Name.ToString() == characterName && member.World.RowId == worldId)
{
requesterInParty = true;
break;
}
}
}
if (requesterInParty)
{
log.Information($"[HelperManager] Request from {characterName}@{worldId} who is ALREADY in my party! Ignoring leave request.");
needsToLeaveParty = false;
}
else
{
needsToLeaveParty = true;
log.Information("[HelperManager] Currently in party (but not with requester), notifying quester...");
crossProcessIPC.NotifyHelperInParty(localName, localWorldId);
if (condition[ConditionFlag.BoundByDuty])
{
isInDuty = true;
log.Information("[HelperManager] Currently in duty, notifying quester...");
crossProcessIPC.NotifyHelperInDuty(localName, localWorldId);
}
}
}
}
@ -427,6 +648,36 @@ public class HelperManager : IDisposable
crossProcessIPC.RequestHelperAnnouncements();
}
private async Task InviteLANHelper(string ipAddress, string helperName, ushort worldId)
{
if (lanHelperClient == null)
{
return;
}
log.Information("[HelperManager] ========================================");
log.Information("[HelperManager] === INVITING LAN HELPER ===");
log.Information("[HelperManager] Helper: " + helperName);
log.Information("[HelperManager] IP: " + ipAddress);
log.Information("[HelperManager] ========================================");
DisbandParty();
await Task.Delay(500);
log.Information("[HelperManager] Sending helper request to " + ipAddress + "...");
if (!(await lanHelperClient.RequestHelperAsync(ipAddress, "LAN Dungeon")))
{
log.Error("[HelperManager] Failed to send helper request to " + ipAddress);
return;
}
await Task.Delay(1000);
log.Information("[HelperManager] Sending party invite to " + helperName + "...");
if (!partyInviteService.InviteToParty(helperName, worldId))
{
log.Error("[HelperManager] Failed to invite " + helperName + " to party");
return;
}
await lanHelperClient.NotifyInviteSentAsync(ipAddress, helperName);
log.Information("[HelperManager] ✓ LAN helper invite complete");
}
public void Dispose()
{
condition.ConditionChange -= OnConditionChanged;