forked from aly/qstbak
qstcompanion v1.0.6
This commit is contained in:
parent
5e1e1decc5
commit
ada27cf05b
30 changed files with 3403 additions and 426 deletions
|
|
@ -21,6 +21,7 @@ using Lumina.Excel;
|
|||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using QuestionableCompanion;
|
||||
using QuestionableCompanion.Models;
|
||||
using QuestionableCompanion.Services;
|
||||
|
||||
public class ChauffeurModeService : IDisposable
|
||||
|
|
@ -81,6 +82,8 @@ public class ChauffeurModeService : IDisposable
|
|||
|
||||
private DateTime? lastZoneChangeTime;
|
||||
|
||||
private DateTime lastDutyExitTime = DateTime.MinValue;
|
||||
|
||||
private bool isFollowingQuester;
|
||||
|
||||
private DateTime lastFollowCheck = DateTime.MinValue;
|
||||
|
|
@ -109,6 +112,14 @@ public class ChauffeurModeService : IDisposable
|
|||
|
||||
public bool IsTransportingQuester => isTransportingQuester;
|
||||
|
||||
public void UpdateQuesterPositionFromLAN(float x, float y, float z, uint zoneId, string questerName)
|
||||
{
|
||||
lastQuesterPosition = new Vector3(x, y, z);
|
||||
lastQuesterZone = zoneId;
|
||||
followingQuesterName = questerName;
|
||||
discoveredQuesters[questerName] = DateTime.Now;
|
||||
}
|
||||
|
||||
public string? GetHelperStatus(string helperKey)
|
||||
{
|
||||
if (!helperStatuses.TryGetValue(helperKey, out string status))
|
||||
|
|
@ -118,6 +129,18 @@ public class ChauffeurModeService : IDisposable
|
|||
return status;
|
||||
}
|
||||
|
||||
public void StartHelperStatusBroadcast()
|
||||
{
|
||||
if (config.IsHighLevelHelper)
|
||||
{
|
||||
log.Information("[ChauffeurMode] Starting periodic helper status broadcast (Helper mode enabled)");
|
||||
framework.RunOnTick(delegate
|
||||
{
|
||||
BroadcastHelperStatusPeriodically();
|
||||
}, TimeSpan.FromSeconds(1L));
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetDiscoveredQuesters()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
|
@ -127,7 +150,19 @@ public class ChauffeurModeService : IDisposable
|
|||
{
|
||||
discoveredQuesters.Remove(stale);
|
||||
}
|
||||
return discoveredQuesters.Keys.ToList();
|
||||
List<string> result = discoveredQuesters.Keys.ToList();
|
||||
LANHelperServer lanServer = Plugin.Instance?.GetLANHelperServer();
|
||||
if (lanServer != null)
|
||||
{
|
||||
foreach (string client in lanServer.GetConnectedClientNames())
|
||||
{
|
||||
if (!result.Contains(client))
|
||||
{
|
||||
result.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ChauffeurModeService(Configuration config, IPluginLog log, IClientState clientState, ICondition condition, IFramework framework, ICommandManager commandManager, IDataManager dataManager, IPartyList partyList, IObjectTable objectTable, QuestionableIPC questionableIPC, CrossProcessIPC crossProcessIPC, PartyInviteService partyInviteService, PartyInviteAutoAccept partyInviteAutoAccept, IDalamudPluginInterface pluginInterface, MemoryHelper memoryHelper, MovementMonitorService? movementMonitor = null)
|
||||
|
|
@ -158,6 +193,7 @@ public class ChauffeurModeService : IDisposable
|
|||
crossProcessIPC.OnHelperStatusUpdate += OnHelperStatusUpdate;
|
||||
crossProcessIPC.OnQuesterPositionUpdate += OnQuesterPositionUpdate;
|
||||
clientState.TerritoryChanged += OnTerritoryChanged;
|
||||
condition.ConditionChange += OnConditionChanged;
|
||||
if (config.IsHighLevelHelper)
|
||||
{
|
||||
framework.RunOnTick(delegate
|
||||
|
|
@ -170,6 +206,15 @@ public class ChauffeurModeService : IDisposable
|
|||
log.Information("[ChauffeurMode] Service initialized");
|
||||
}
|
||||
|
||||
private void OnConditionChanged(ConditionFlag flag, bool value)
|
||||
{
|
||||
if (flag == ConditionFlag.BoundByDuty && !value)
|
||||
{
|
||||
lastDutyExitTime = DateTime.Now;
|
||||
log.Information("[ChauffeurMode] Left duty - starting 10s grace period for zone checks");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFrameworkUpdate(IFramework framework)
|
||||
{
|
||||
if (config.IsHighLevelHelper && config.EnableHelperFollowing && (DateTime.Now - lastFollowCheck).TotalSeconds >= (double)config.HelperFollowCheckInterval)
|
||||
|
|
@ -202,61 +247,72 @@ public class ChauffeurModeService : IDisposable
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (objectTable.LocalPlayer == null)
|
||||
double timeSinceDutyExit = (DateTime.Now - lastDutyExitTime).TotalSeconds;
|
||||
if (timeSinceDutyExit < 10.0)
|
||||
{
|
||||
return;
|
||||
if (timeSinceDutyExit < 1.0 || timeSinceDutyExit > 9.0)
|
||||
{
|
||||
log.Debug($"[WaitTerritory] Duty Grace Period: Waiting for stabilization after duty exit (elapsed: {timeSinceDutyExit:F1}s / 10.0s)");
|
||||
}
|
||||
}
|
||||
object task = questionableIPC.GetCurrentTask();
|
||||
if (task == null)
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!(task is JObject jObject))
|
||||
if (objectTable.LocalPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
JToken taskNameToken = jObject["TaskName"];
|
||||
if (taskNameToken == null)
|
||||
object task = questionableIPC.GetCurrentTask();
|
||||
if (task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string taskName = taskNameToken.ToString();
|
||||
if (string.IsNullOrEmpty(taskName))
|
||||
try
|
||||
{
|
||||
return;
|
||||
}
|
||||
Match waitTerritoryMatch = new Regex("Wait\\(territory:\\s*(.+?)\\s*\\((\\d+)\\)\\)").Match(taskName);
|
||||
if (!waitTerritoryMatch.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string territoryName = waitTerritoryMatch.Groups[1].Value.Trim();
|
||||
uint territoryId = uint.Parse(waitTerritoryMatch.Groups[2].Value);
|
||||
if (clientState.TerritoryType == territoryId)
|
||||
{
|
||||
log.Debug($"[WaitTerritory] Already in target territory {territoryName} ({territoryId}) - skipping teleport");
|
||||
return;
|
||||
}
|
||||
string mappedName = MapTerritoryName(territoryName);
|
||||
log.Information($"[WaitTerritory] Wait(territory) detected: {territoryName} ({territoryId}) → Auto-teleporting to {mappedName}");
|
||||
framework.RunOnFrameworkThread(delegate
|
||||
{
|
||||
try
|
||||
if (!(task is JObject jObject))
|
||||
{
|
||||
string content = "/li " + mappedName;
|
||||
commandManager.ProcessCommand(content);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
JToken taskNameToken = jObject["TaskName"];
|
||||
if (taskNameToken == null)
|
||||
{
|
||||
log.Error("[WaitTerritory] Failed to teleport to " + mappedName + ": " + ex2.Message);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("[WaitTerritory] Error checking Wait(territory) task: " + ex.Message);
|
||||
string taskName = taskNameToken.ToString();
|
||||
if (string.IsNullOrEmpty(taskName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Match waitTerritoryMatch = new Regex("Wait\\(territory:\\s*(.+?)\\s*\\((\\d+)\\)\\)").Match(taskName);
|
||||
if (!waitTerritoryMatch.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string territoryName = waitTerritoryMatch.Groups[1].Value.Trim();
|
||||
uint territoryId = uint.Parse(waitTerritoryMatch.Groups[2].Value);
|
||||
if (clientState.TerritoryType == territoryId)
|
||||
{
|
||||
log.Debug($"[WaitTerritory] Already in target territory {territoryName} ({territoryId}) - skipping teleport");
|
||||
return;
|
||||
}
|
||||
string mappedName = MapTerritoryName(territoryName);
|
||||
log.Information($"[WaitTerritory] Wait(territory) detected: {territoryName} ({territoryId}) → Auto-teleporting to {mappedName}");
|
||||
framework.RunOnFrameworkThread(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
string content = "/li " + mappedName;
|
||||
commandManager.ProcessCommand(content);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
log.Error("[WaitTerritory] Failed to teleport to " + mappedName + ": " + ex2.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("[WaitTerritory] Error checking Wait(territory) task: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,6 +336,10 @@ public class ChauffeurModeService : IDisposable
|
|||
return;
|
||||
}
|
||||
}
|
||||
if ((DateTime.Now - lastDutyExitTime).TotalSeconds < 10.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ushort currentZoneId = clientState.TerritoryType;
|
||||
if (BLACKLISTED_ZONES.Contains(currentZoneId))
|
||||
{
|
||||
|
|
@ -587,6 +647,14 @@ public class ChauffeurModeService : IDisposable
|
|||
log.Information("[ChauffeurMode] ========================================");
|
||||
log.Information("[ChauffeurMode] === SUMMONING HELPER ===");
|
||||
log.Information("[ChauffeurMode] ========================================");
|
||||
if (config.HelperSelection == HelperSelectionMode.ManualInput)
|
||||
{
|
||||
log.Warning("[ChauffeurMode] [QUESTER] Manual Input mode is selected!");
|
||||
log.Warning("[ChauffeurMode] [QUESTER] Chauffeur Mode requires IPC communication and cannot work with Manual Input.");
|
||||
log.Warning("[ChauffeurMode] [QUESTER] Please switch to 'Auto' or 'Dropdown' mode to use Chauffeur.");
|
||||
log.Warning("[ChauffeurMode] [QUESTER] Walking to destination instead.");
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(config.PreferredHelper))
|
||||
{
|
||||
string preferredHelper = config.PreferredHelper;
|
||||
|
|
@ -660,7 +728,88 @@ public class ChauffeurModeService : IDisposable
|
|||
log.Information($"[ChauffeurMode] Quester Position: ({questerPos.X:F2}, {questerPos.Y:F2}, {questerPos.Z:F2})");
|
||||
log.Information($"[ChauffeurMode] Target: ({targetPos.X:F2}, {targetPos.Y:F2}, {targetPos.Z:F2})");
|
||||
log.Information($"[ChauffeurMode] AttuneAetheryte: {isAttuneAetheryte}");
|
||||
crossProcessIPC.SendChauffeurSummonRequest(questerName, questerWorld, zoneId, targetPos, questerPos, isAttuneAetheryte);
|
||||
bool isLanHelper = false;
|
||||
string lanIp = null;
|
||||
log.Information("[ChauffeurMode] Checking if preferred helper '" + config.PreferredHelper + "' is a LAN helper...");
|
||||
LANHelperClient lanClient = Plugin.Instance?.GetLANHelperClient();
|
||||
if (lanClient != null)
|
||||
{
|
||||
IReadOnlyList<LANHelperInfo> lanHelpers = lanClient.DiscoveredHelpers;
|
||||
log.Information($"[ChauffeurMode] Found {lanHelpers.Count} LAN helpers in discovery list");
|
||||
if (!string.IsNullOrEmpty(config.PreferredHelper))
|
||||
{
|
||||
foreach (LANHelperInfo helper in lanHelpers)
|
||||
{
|
||||
string helperKey = $"{helper.Name}@{helper.WorldId}";
|
||||
log.Information("[ChauffeurMode] Checking LAN helper: " + helperKey + " at " + helper.IPAddress);
|
||||
if (helperKey == config.PreferredHelper)
|
||||
{
|
||||
isLanHelper = true;
|
||||
lanIp = helper.IPAddress;
|
||||
log.Information("[ChauffeurMode] ✓ MATCHED! This is a LAN helper at " + lanIp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isLanHelper)
|
||||
{
|
||||
log.Information("[ChauffeurMode] No match found - PreferredHelper '" + config.PreferredHelper + "' not in LAN list");
|
||||
}
|
||||
}
|
||||
else if (lanHelpers.Any((LANHelperInfo h) => h.Status == LANHelperStatus.Available))
|
||||
{
|
||||
LANHelperInfo firstAvailable = lanHelpers.FirstOrDefault((LANHelperInfo h) => h.Status == LANHelperStatus.Available);
|
||||
if (firstAvailable != null)
|
||||
{
|
||||
isLanHelper = true;
|
||||
lanIp = firstAvailable.IPAddress;
|
||||
string autoSelectedKey = $"{firstAvailable.Name}@{firstAvailable.WorldId}";
|
||||
log.Information("[ChauffeurMode] AUTO-SELECTED LAN helper: " + autoSelectedKey + " at " + lanIp);
|
||||
}
|
||||
}
|
||||
else if (lanHelpers.Count > 0)
|
||||
{
|
||||
LANHelperInfo firstHelper = lanHelpers.First();
|
||||
isLanHelper = true;
|
||||
lanIp = firstHelper.IPAddress;
|
||||
string autoSelectedKey2 = $"{firstHelper.Name}@{firstHelper.WorldId}";
|
||||
log.Information("[ChauffeurMode] AUTO-SELECTED first LAN helper (no Available status): " + autoSelectedKey2 + " at " + lanIp);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Information("[ChauffeurMode] No PreferredHelper configured and no LAN helpers available - using local IPC");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Warning("[ChauffeurMode] LANHelperClient is null!");
|
||||
log.Information("[ChauffeurMode] Falling back to local IPC");
|
||||
}
|
||||
if (isLanHelper && !string.IsNullOrEmpty(lanIp))
|
||||
{
|
||||
log.Information("[ChauffeurMode] Selected helper is on LAN (" + lanIp + ") - Sending LAN Summon Request");
|
||||
if (lanClient != null)
|
||||
{
|
||||
LANChauffeurSummon summonData = new LANChauffeurSummon
|
||||
{
|
||||
QuesterName = questerName,
|
||||
QuesterWorldId = questerWorld,
|
||||
ZoneId = zoneId,
|
||||
TargetX = targetPos.X,
|
||||
TargetY = targetPos.Y,
|
||||
TargetZ = targetPos.Z,
|
||||
QuesterX = questerPos.X,
|
||||
QuesterY = questerPos.Y,
|
||||
QuesterZ = questerPos.Z,
|
||||
IsAttuneAetheryte = isAttuneAetheryte
|
||||
};
|
||||
lanClient.SendChauffeurSummonAsync(lanIp, summonData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Information("[ChauffeurMode] Sending local IPC Summon Request");
|
||||
crossProcessIPC.SendChauffeurSummonRequest(questerName, questerWorld, zoneId, targetPos, questerPos, isAttuneAetheryte);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRestrictedZone(uint zoneId)
|
||||
|
|
@ -756,15 +905,25 @@ public class ChauffeurModeService : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
log.Debug($"[ChauffeurMode] Found {mounts.Count} multi-seater mounts");
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
log.Error("[ChauffeurMode] Error loading multi-seater mounts: " + ex.Message);
|
||||
}
|
||||
return mounts;
|
||||
}
|
||||
|
||||
public void StartHelperWorkflow(string questerName, ushort questerWorld, uint zoneId, Vector3 targetPos, Vector3 questerPos, bool isAttuneAetheryte)
|
||||
{
|
||||
log.Information("[ChauffeurMode] =========================================");
|
||||
log.Information("[ChauffeurMode] *** StartHelperWorkflow CALLED ***");
|
||||
log.Information("[ChauffeurMode] =========================================");
|
||||
log.Information($"[ChauffeurMode] Quester: {questerName}@{questerWorld}");
|
||||
log.Information($"[ChauffeurMode] Zone: {zoneId}");
|
||||
log.Information($"[ChauffeurMode] Target: ({targetPos.X:F2}, {targetPos.Y:F2}, {targetPos.Z:F2})");
|
||||
log.Information($"[ChauffeurMode] AttuneAetheryte: {isAttuneAetheryte}");
|
||||
OnChauffeurSummonRequest(questerName, questerWorld, zoneId, targetPos, questerPos, isAttuneAetheryte);
|
||||
}
|
||||
|
||||
private void OnChauffeurSummonRequest(string questerName, ushort questerWorld, uint zoneId, Vector3 targetPos, Vector3 questerPos, bool isAttuneAetheryte)
|
||||
{
|
||||
if (!config.ChauffeurModeEnabled)
|
||||
|
|
@ -1274,6 +1433,12 @@ public class ChauffeurModeService : IDisposable
|
|||
log.Information($"[ChauffeurMode] [HELPER] Helper position: ({objectTable.LocalPlayer?.Position.X:F2}, {objectTable.LocalPlayer?.Position.Y:F2}, {objectTable.LocalPlayer?.Position.Z:F2})");
|
||||
crossProcessIPC.SendChauffeurMountReady(questerName, questerWorld);
|
||||
log.Information("[ChauffeurMode] [HELPER] Mount ready signal sent via IPC");
|
||||
LANHelperServer lanServer = Plugin.Instance?.GetLANHelperServer();
|
||||
if (lanServer != null)
|
||||
{
|
||||
log.Information("[ChauffeurMode] [HELPER] Also sending mount ready via LAN to connected clients");
|
||||
lanServer.SendChauffeurMountReady(questerName, questerWorld);
|
||||
}
|
||||
log.Information("[ChauffeurMode] [WORKFLOW] Waiting 8 seconds for quester to mount...");
|
||||
await Task.Delay(8000);
|
||||
log.Information($"[ChauffeurMode] [WORKFLOW] Step 6: Transporting to target ({finalTargetPos.X:F2}, {finalTargetPos.Y:F2}, {finalTargetPos.Z:F2})");
|
||||
|
|
@ -1333,6 +1498,12 @@ public class ChauffeurModeService : IDisposable
|
|||
log.Information("[ChauffeurMode] [HELPER] Transport complete - FLAGS RESET + STATUS AVAILABLE (before notification)");
|
||||
log.Information($"[ChauffeurMode] [HELPER] Notifying Quester of arrival: {questerName}@{questerWorld}");
|
||||
crossProcessIPC.SendChauffeurArrived(questerName, questerWorld);
|
||||
LANHelperServer lanServerArrival = Plugin.Instance?.GetLANHelperServer();
|
||||
if (lanServerArrival != null)
|
||||
{
|
||||
log.Information("[ChauffeurMode] [HELPER] Also sending arrival via LAN to connected clients");
|
||||
lanServerArrival.SendChauffeurArrived(questerName, questerWorld);
|
||||
}
|
||||
log.Information("[ChauffeurMode] [HELPER] Waiting for quester to restart Questionable and checking for AttuneAetheryte task...");
|
||||
await Task.Delay(3000);
|
||||
bool isAttuneAetheryteTask = false;
|
||||
|
|
@ -1954,7 +2125,7 @@ public class ChauffeurModeService : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe void OnChauffeurMountReady(string questerName, ushort questerWorld)
|
||||
public unsafe void OnChauffeurMountReady(string questerName, ushort questerWorld)
|
||||
{
|
||||
if (!config.ChauffeurModeEnabled || !config.IsQuester || !isWaitingForHelper)
|
||||
{
|
||||
|
|
@ -2306,7 +2477,7 @@ public class ChauffeurModeService : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void OnChauffeurArrived(string questerName, ushort questerWorld)
|
||||
public void OnChauffeurArrived(string questerName, ushort questerWorld)
|
||||
{
|
||||
if (!config.ChauffeurModeEnabled || !config.IsQuester || !isWaitingForHelper)
|
||||
{
|
||||
|
|
@ -2747,13 +2918,24 @@ public class ChauffeurModeService : IDisposable
|
|||
lastZoneChangeTime = null;
|
||||
}
|
||||
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
||||
if (localPlayer != null)
|
||||
if (localPlayer == null)
|
||||
{
|
||||
string questerName = localPlayer.Name.ToString();
|
||||
ushort questerWorld = (ushort)localPlayer.HomeWorld.RowId;
|
||||
ushort currentZone = clientState.TerritoryType;
|
||||
Vector3 position = localPlayer.Position;
|
||||
crossProcessIPC.BroadcastQuesterPosition(questerName, questerWorld, currentZone, position);
|
||||
return;
|
||||
}
|
||||
string questerName = localPlayer.Name.ToString();
|
||||
ushort questerWorld = (ushort)localPlayer.HomeWorld.RowId;
|
||||
ushort currentZone = clientState.TerritoryType;
|
||||
Vector3 position = localPlayer.Position;
|
||||
crossProcessIPC.BroadcastQuesterPosition(questerName, questerWorld, currentZone, position);
|
||||
LANHelperClient lanClient = Plugin.Instance?.GetLANHelperClient();
|
||||
if (lanClient != null)
|
||||
{
|
||||
IReadOnlyList<LANHelperInfo> lanHelpers = lanClient.DiscoveredHelpers;
|
||||
if (lanHelpers.Count > 0)
|
||||
{
|
||||
LANHelperInfo firstHelper = lanHelpers.First();
|
||||
lanClient.SendFollowCommandAsync(firstHelper.IPAddress, position.X, position.Y, position.Z, currentZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue