qstcompanion v1.0.5

This commit is contained in:
alydev 2025-12-04 04:40:50 +10:00
parent 52daefcfd7
commit 5e1e1decc5
9 changed files with 304 additions and 200 deletions

View file

@ -202,7 +202,7 @@ public class ChauffeurModeService : IDisposable
return;
}
}
if (clientState.LocalPlayer == null)
if (objectTable.LocalPlayer == null)
{
return;
}
@ -266,7 +266,7 @@ public class ChauffeurModeService : IDisposable
{
return;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return;
@ -574,7 +574,7 @@ public class ChauffeurModeService : IDisposable
private async void SummonHelper(Vector3 targetPos, uint zoneId)
{
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return;
@ -789,7 +789,7 @@ public class ChauffeurModeService : IDisposable
this.questerName = $"{questerName}@{questerWorld}";
targetZoneId = zoneId;
targetPosition = targetPos;
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string myName = localPlayer.Name.ToString();
@ -881,7 +881,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
IPlayerCharacter localPlayer2 = clientState.LocalPlayer;
IPlayerCharacter localPlayer2 = objectTable.LocalPlayer;
if (localPlayer2 == null)
{
log.Error("[ChauffeurMode] [WORKFLOW] LocalPlayer is null!");
@ -1180,7 +1180,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
IPlayerCharacter localPlayer2 = clientState.LocalPlayer;
IPlayerCharacter localPlayer2 = objectTable.LocalPlayer;
if (localPlayer2 != null)
{
float result = Vector3.Distance(localPlayer2.Position, questerPos);
@ -1271,7 +1271,7 @@ public class ChauffeurModeService : IDisposable
log.Information("[ChauffeurMode] [HELPER] ========================================");
log.Information($"[ChauffeurMode] [HELPER] Sending mount ready signal to: {questerName}@{questerWorld}");
log.Information($"[ChauffeurMode] [HELPER] Helper is mounted: {IsMounted()}");
log.Information($"[ChauffeurMode] [HELPER] Helper position: ({clientState.LocalPlayer?.Position.X:F2}, {clientState.LocalPlayer?.Position.Y:F2}, {clientState.LocalPlayer?.Position.Z:F2})");
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");
log.Information("[ChauffeurMode] [WORKFLOW] Waiting 8 seconds for quester to mount...");
@ -1323,7 +1323,7 @@ public class ChauffeurModeService : IDisposable
config.AssignedQuester = "";
config.CurrentHelperStatus = HelperStatus.Available;
config.Save();
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string helperName = localPlayer.Name.ToString();
@ -1363,7 +1363,7 @@ public class ChauffeurModeService : IDisposable
if (isAttuneAetheryteTask && targetPosition.HasValue)
{
log.Information("[ChauffeurMode] [HELPER] AttuneAetheryte detected - flying 10 yalms away from target before dismount");
Vector3 direction = Vector3.Normalize(await framework.RunOnFrameworkThread(() => clientState.LocalPlayer?.Position ?? Vector3.Zero) - targetPosition.Value);
Vector3 direction = Vector3.Normalize(await framework.RunOnFrameworkThread(() => objectTable.LocalPlayer?.Position ?? Vector3.Zero) - targetPosition.Value);
Vector3 flyAwayPosition = targetPosition.Value + direction * 10f;
log.Information($"[ChauffeurMode] [HELPER] Flying to position 10 yalms away: ({flyAwayPosition.X:F2}, {flyAwayPosition.Y:F2}, {flyAwayPosition.Z:F2})");
await framework.RunOnFrameworkThread(delegate
@ -1382,7 +1382,7 @@ public class ChauffeurModeService : IDisposable
DateTime timeout = DateTime.Now.AddSeconds(10.0);
while (DateTime.Now < timeout)
{
float distanceToTarget = Vector3.Distance(await framework.RunOnFrameworkThread(() => clientState.LocalPlayer?.Position ?? Vector3.Zero), targetPosition.Value);
float distanceToTarget = Vector3.Distance(await framework.RunOnFrameworkThread(() => objectTable.LocalPlayer?.Position ?? Vector3.Zero), targetPosition.Value);
if (distanceToTarget >= 10f)
{
log.Information($"[ChauffeurMode] [HELPER] Successfully flew away (distance: {distanceToTarget:F2} yalms)");
@ -1535,7 +1535,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
if (clientState.LocalPlayer == null)
if (objectTable.LocalPlayer == null)
{
return false;
}
@ -1621,7 +1621,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
posTask.SetResult(localPlayer?.Position);
}
catch (Exception ex2)
@ -1737,7 +1737,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
Vector3? result = clientState.LocalPlayer?.Position;
Vector3? result = objectTable.LocalPlayer?.Position;
helperStartPosTask.SetResult(result);
}
catch
@ -1759,7 +1759,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
Vector3? result = clientState.LocalPlayer?.Position;
Vector3? result = objectTable.LocalPlayer?.Position;
helperCurrentPosTask.SetResult(result);
}
catch
@ -1868,7 +1868,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
Vector3? result = clientState.LocalPlayer?.Position;
Vector3? result = objectTable.LocalPlayer?.Position;
posTask.SetResult(result);
}
catch
@ -1965,7 +1965,7 @@ public class ChauffeurModeService : IDisposable
log.Debug("[ChauffeurMode] [QUESTER] RidePillion already executed this session, ignoring");
return;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return;
@ -2046,7 +2046,7 @@ public class ChauffeurModeService : IDisposable
{
try
{
IPlayerCharacter localPlayer2 = clientState.LocalPlayer;
IPlayerCharacter localPlayer2 = objectTable.LocalPlayer;
if (localPlayer2 == null)
{
log.Error("[ChauffeurMode] [QUESTER] Local player is null");
@ -2270,7 +2270,7 @@ public class ChauffeurModeService : IDisposable
}
return;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
try
@ -2312,7 +2312,7 @@ public class ChauffeurModeService : IDisposable
{
return;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return;
@ -2428,7 +2428,7 @@ public class ChauffeurModeService : IDisposable
if (config.IsQuester && !((DateTime.Now - lastZoneUpdate).TotalSeconds < 5.0))
{
lastZoneUpdate = DateTime.Now;
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string zoneName = GetZoneName(territoryId);
@ -2493,7 +2493,7 @@ public class ChauffeurModeService : IDisposable
config.AssignedQuester = "";
config.CurrentHelperStatus = HelperStatus.Available;
config.Save();
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string helperName = localPlayer.Name.ToString();
@ -2533,7 +2533,7 @@ public class ChauffeurModeService : IDisposable
config.CurrentHelperStatus = HelperStatus.Available;
config.Save();
log.Information("[ChauffeurMode] [HELPER] Status: Available");
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string helperName = localPlayer.Name.ToString();
@ -2614,7 +2614,7 @@ public class ChauffeurModeService : IDisposable
return;
}
lastFollowCheck = now;
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return;
@ -2746,7 +2746,7 @@ public class ChauffeurModeService : IDisposable
log.Information($"[ChauffeurMode] [HelperFollowing] Territory Load State: Zone load complete ({timeSinceZoneChange:F1}s) - resuming position broadcasts");
lastZoneChangeTime = null;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
string questerName = localPlayer.Name.ToString();
@ -2791,7 +2791,7 @@ public class ChauffeurModeService : IDisposable
{
return null;
}
IPlayerCharacter localPlayer = clientState.LocalPlayer;
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
return null;

View file

@ -267,7 +267,6 @@ public class AutoRetainerIPC : IDisposable
{
resolvedName = name2 + "@" + world2;
characterCache[cid] = resolvedName;
log.Debug($"[AutoRetainerIPC] CID {cid} resolved to {resolvedName} (via properties)");
return resolvedName;
}
}
@ -277,7 +276,6 @@ public class AutoRetainerIPC : IDisposable
if (!string.IsNullOrEmpty(resolvedName))
{
characterCache[cid] = resolvedName;
log.Debug($"[AutoRetainerIPC] CID {cid} resolved to {resolvedName} (via JSON)");
return resolvedName;
}
}

View file

@ -55,8 +55,6 @@ public class DungeonAutomationService : IDisposable
private bool isAutomationActive;
private int originalDutyMode;
private bool hasSentAtY;
public bool IsWaitingForParty => isWaitingForParty;

View file

@ -18,7 +18,7 @@ public class MSQProgressionService
private readonly QuestDetectionService questDetectionService;
private readonly IClientState clientState;
private readonly IObjectTable objectTable;
private readonly IFramework framework;
@ -96,12 +96,12 @@ public class MSQProgressionService
}
};
public MSQProgressionService(IDataManager dataManager, IPluginLog log, QuestDetectionService questDetectionService, IClientState clientState, IFramework framework)
public MSQProgressionService(IDataManager dataManager, IPluginLog log, QuestDetectionService questDetectionService, IObjectTable objectTable, IFramework framework)
{
this.dataManager = dataManager;
this.log = log;
this.questDetectionService = questDetectionService;
this.clientState = clientState;
this.objectTable = objectTable;
this.framework = framework;
InitializeMSQData();
framework.RunOnTick(delegate
@ -114,95 +114,68 @@ public class MSQProgressionService
{
try
{
log.Information("[MSQProgression] === INITIALIZING MSQ DATA ===");
ExcelSheet<Quest> questSheet = dataManager.GetExcelSheet<Quest>();
if (questSheet == null)
{
log.Error("[MSQProgression] Failed to load Quest sheet from Lumina!");
return;
}
int totalQuests = questSheet.Count();
log.Information($"[MSQProgression] ✓ Lumina Quest Sheet loaded: {totalQuests} total quests");
questSheet.Count();
int manualCount = 0;
foreach (Quest item in questSheet)
{
_ = item;
manualCount++;
}
log.Information($"[MSQProgression] Manual iteration count: {manualCount} quests");
List<Quest> highIdQuests = questSheet.Where((Quest q) => q.RowId > 66000).ToList();
log.Information($"[MSQProgression] Quests with RowId > 66000: {highIdQuests.Count}");
if (highIdQuests.Count > 0)
{
Quest firstHighId = highIdQuests.First();
log.Information($"[MSQProgression] First High ID Quest: {firstHighId.RowId}");
log.Information($"[MSQProgression] - Name: {firstHighId.Name}");
log.Information($"[MSQProgression] - Expansion.RowId: {firstHighId.Expansion.RowId}");
log.Information($"[MSQProgression] - JournalGenre.RowId: {firstHighId.JournalGenre.RowId}");
highIdQuests.First();
}
log.Information("[MSQProgression] Analyzing JournalGenre distribution...");
foreach (IGrouping<uint, Quest> group in (from q in questSheet
foreach (IGrouping<uint, Quest> item2 in (from q in questSheet
where q.RowId != 0
group q by q.JournalGenre.RowId into g
orderby g.Key
select g).Take(10))
{
log.Information($"[MSQProgression] Genre {group.Key}: {group.Count()} quests");
_ = item2;
}
log.Information("[MSQProgression] Filtering MSQ quests by JournalGenre categories (1-14)...");
mainScenarioQuests = (from q in questSheet
where ((ReadOnlySpan<uint>)MSQ_JOURNAL_GENRE_IDS).Contains(q.JournalGenre.RowId)
orderby q.RowId
select q).ToList();
log.Information($"[MSQProgression] ✓ Found {mainScenarioQuests.Count} total MSQ quests across all expansions!");
if (mainScenarioQuests.Count == 0)
{
log.Error("[MSQProgression] No MSQ quests found! JournalGenre filter may be incorrect.");
return;
}
log.Information("[MSQProgression] === DETAILED MSQ QUEST ANALYSIS (First 20) ===");
foreach (Quest quest in mainScenarioQuests.Take(20))
{
log.Information($"[MSQProgression] Quest {quest.RowId}:");
log.Information($"[MSQProgression] - Name: {quest.Name}");
log.Information($"[MSQProgression] - Expansion.RowId: {quest.Expansion.RowId}");
try
{
string expansionName = quest.Expansion.Value.Name.ToString();
log.Information("[MSQProgression] - Expansion.Name: " + expansionName);
quest.Expansion.Value.Name.ToString();
}
catch (Exception ex)
catch (Exception)
{
log.Information("[MSQProgression] - Expansion.Name: ERROR - " + ex.Message);
}
log.Information($"[MSQProgression] - JournalGenre.RowId: {quest.JournalGenre.RowId}");
}
log.Information("[MSQProgression] === MSQ QUESTS BY JOURNALGENRE (EXPANSION) ===");
foreach (IGrouping<uint, Quest> group2 in from q in mainScenarioQuests
foreach (IGrouping<uint, Quest> group in from q in mainScenarioQuests
group q by q.JournalGenre.RowId into g
orderby g.Key
select g)
{
uint genreId = group2.Key;
MSQExpansionData.Expansion expansion = JournalGenreToExpansion.GetValueOrDefault(genreId, MSQExpansionData.Expansion.ARealmReborn);
string genreName = group2.First().JournalGenre.Value.Name.ToString();
string sampleQuests = string.Join(", ", from q in group2.Take(3)
uint genreId = group.Key;
JournalGenreToExpansion.GetValueOrDefault(genreId, MSQExpansionData.Expansion.ARealmReborn);
group.First().JournalGenre.Value.Name.ToString();
string.Join(", ", from q in @group.Take(3)
select q.RowId);
log.Information($"[MSQProgression] JournalGenre {genreId} ({expansion}):");
log.Information("[MSQProgression] - Name: " + genreName);
log.Information($"[MSQProgression] - Count: {group2.Count()} quests");
log.Information("[MSQProgression] - Samples: " + sampleQuests + "...");
}
log.Information("[MSQProgression] === MSQ QUESTS BY EXPANSION (GROUPED) ===");
foreach (IGrouping<MSQExpansionData.Expansion, Quest> group3 in from q in mainScenarioQuests
foreach (IGrouping<MSQExpansionData.Expansion, Quest> item3 in from q in mainScenarioQuests
group q by JournalGenreToExpansion.GetValueOrDefault(q.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn) into g
orderby g.Key
select g)
{
log.Information($"[MSQProgression] {group3.Key}: {group3.Count()} quests total");
_ = item3;
}
MSQExpansionData.ClearQuests();
log.Information("[MSQProgression] Building expansion quest mappings...");
foreach (Quest quest2 in mainScenarioQuests)
{
string name = quest2.Name.ToString();
@ -210,11 +183,11 @@ public class MSQProgressionService
{
questNameCache[quest2.RowId] = name;
}
MSQExpansionData.Expansion expansion2 = JournalGenreToExpansion.GetValueOrDefault(quest2.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn);
MSQExpansionData.Expansion expansion = JournalGenreToExpansion.GetValueOrDefault(quest2.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn);
if (quest2.JournalGenre.RowId != 2 || quest2.RowId <= 65964)
{
MSQExpansionData.RegisterQuest(quest2.RowId, expansion2);
string shortName = MSQExpansionData.GetExpansionShortName(expansion2);
MSQExpansionData.RegisterQuest(quest2.RowId, expansion);
string shortName = MSQExpansionData.GetExpansionShortName(expansion);
if (!questsByExpansion.ContainsKey(shortName))
{
questsByExpansion[shortName] = new List<Quest>();
@ -222,29 +195,19 @@ public class MSQProgressionService
questsByExpansion[shortName].Add(quest2);
}
}
log.Information("[MSQProgression] === EXPANSION BREAKDOWN ===");
foreach (MSQExpansionData.Expansion exp in MSQExpansionData.GetAllExpansions())
foreach (MSQExpansionData.Expansion allExpansion in MSQExpansionData.GetAllExpansions())
{
string shortName2 = MSQExpansionData.GetExpansionShortName(exp);
string shortName2 = MSQExpansionData.GetExpansionShortName(allExpansion);
List<Quest> quests = questsByExpansion.GetValueOrDefault(shortName2);
int count = quests?.Count ?? 0;
if (count > 0 && quests != null)
if ((quests?.Count ?? 0) > 0 && quests != null)
{
string sampleIds = string.Join(", ", from q in quests.Take(5)
string.Join(", ", from q in quests.Take(5)
select q.RowId);
log.Information($"[MSQProgression] ✓ {MSQExpansionData.GetExpansionName(exp)} ({shortName2}): {count} quests (IDs: {sampleIds}...)");
}
else
}
}
catch (Exception)
{
log.Warning($"[MSQProgression] ⚠ {MSQExpansionData.GetExpansionName(exp)} ({shortName2}): {count} quests (EMPTY!)");
}
}
log.Information("[MSQProgression] === MSQ DATA INITIALIZATION COMPLETE ===");
}
catch (Exception ex2)
{
log.Error("[MSQProgression] EXCEPTION during MSQ data initialization: " + ex2.Message);
log.Error("[MSQProgression] Stack trace: " + ex2.StackTrace);
}
}
@ -267,9 +230,8 @@ public class MSQProgressionService
return (questId: lastMSQ.RowId, questName: questName);
}
}
catch (Exception ex)
catch (Exception)
{
log.Error("[MSQProgression] Failed to get last completed MSQ: " + ex.Message);
}
return (questId: 0u, questName: "—");
}
@ -285,9 +247,8 @@ public class MSQProgressionService
List<uint> completedQuests = questDetectionService.GetAllCompletedQuestIds();
return (float)mainScenarioQuests.Count((Quest q) => completedQuests.Contains(q.RowId)) / (float)mainScenarioQuests.Count * 100f;
}
catch (Exception ex)
catch (Exception)
{
log.Error("[MSQProgression] Failed to calculate MSQ completion: " + ex.Message);
return 0f;
}
}
@ -308,9 +269,8 @@ public class MSQProgressionService
List<uint> completedQuests = questDetectionService.GetAllCompletedQuestIds();
return mainScenarioQuests.Count((Quest q) => completedQuests.Contains(q.RowId));
}
catch (Exception ex)
catch (Exception)
{
log.Error("[MSQProgression] Failed to get completed MSQ count: " + ex.Message);
return 0;
}
}
@ -364,49 +324,25 @@ public class MSQProgressionService
{
try
{
log.Information("[MSQProgression] ========================================");
log.Information("[MSQProgression] === DETECTING CURRENT EXPANSION ===");
log.Information("[MSQProgression] ========================================");
List<uint> completedQuests = questDetectionService.GetAllCompletedQuestIds();
log.Information($"[MSQProgression] Total completed quests: {completedQuests.Count}");
log.Information("[MSQProgression] METHOD 1: Using AgentScenarioTree (Game Data)");
log.Information("[MSQProgression] ------------------------------------------------");
(MSQExpansionData.Expansion expansion, string debugInfo) currentExpansionFromGameWithDebug = MSQExpansionData.GetCurrentExpansionFromGameWithDebug();
MSQExpansionData.Expansion gameExpansion = currentExpansionFromGameWithDebug.expansion;
string[] array = currentExpansionFromGameWithDebug.debugInfo.Split('\n');
foreach (string line in array)
for (int i = 0; i < array.Length; i++)
{
if (!string.IsNullOrWhiteSpace(line))
{
log.Information("[MSQProgression] " + line);
string.IsNullOrWhiteSpace(array[i]);
}
}
log.Information("[MSQProgression] Game Data Result: " + MSQExpansionData.GetExpansionName(gameExpansion));
log.Information("[MSQProgression] METHOD 2: Using Completed Quests Analysis");
log.Information("[MSQProgression] ------------------------------------------------");
MSQExpansionData.Expansion analysisExpansion = MSQExpansionData.GetCurrentExpansion(completedQuests);
log.Information("[MSQProgression] Analysis Result: " + MSQExpansionData.GetExpansionName(analysisExpansion));
array = MSQExpansionData.GetExpansionDetectionDebugInfo(completedQuests).Split('\n');
foreach (string line2 in array)
for (int i = 0; i < array.Length; i++)
{
if (!string.IsNullOrWhiteSpace(line2))
{
log.Debug("[MSQProgression] " + line2);
string.IsNullOrWhiteSpace(array[i]);
}
}
log.Information("[MSQProgression] COMPARISON:");
log.Information("[MSQProgression] Game Data: " + MSQExpansionData.GetExpansionName(gameExpansion));
log.Information("[MSQProgression] Analysis: " + MSQExpansionData.GetExpansionName(analysisExpansion));
MSQExpansionData.Expansion finalExpansion = gameExpansion;
if (gameExpansion == MSQExpansionData.Expansion.ARealmReborn && analysisExpansion != MSQExpansionData.Expansion.ARealmReborn)
{
log.Warning("[MSQProgression] Game data returned ARR but analysis found higher expansion!");
log.Warning("[MSQProgression] Using analysis result: " + MSQExpansionData.GetExpansionName(analysisExpansion));
finalExpansion = analysisExpansion;
}
log.Information("[MSQProgression] ========================================");
log.Information("[MSQProgression] >>> FINAL EXPANSION: " + MSQExpansionData.GetExpansionName(finalExpansion) + " <<<");
log.Information("[MSQProgression] ========================================");
return new ExpansionInfo
{
Name = MSQExpansionData.GetExpansionName(finalExpansion),
@ -416,10 +352,8 @@ public class MSQProgressionService
ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(finalExpansion)
};
}
catch (Exception ex)
catch (Exception)
{
log.Error("[MSQProgression] Error detecting expansion: " + ex.Message);
log.Error("[MSQProgression] Stack: " + ex.StackTrace);
return GetExpansions().FirstOrDefault();
}
}
@ -514,28 +448,23 @@ public class MSQProgressionService
{
try
{
log.Information("[MSQProgression] === DEBUG CURRENT CHARACTER QUEST ===");
IPlayerCharacter player = clientState.LocalPlayer;
IPlayerCharacter player = objectTable.LocalPlayer;
if (player == null)
{
log.Warning("[MSQProgression] LocalPlayer is null - not logged in yet?");
framework.RunOnTick(delegate
{
DebugCurrentCharacterQuest();
}, default(TimeSpan), 60);
return;
}
string characterName = player.Name.TextValue;
string worldName = player.HomeWorld.Value.Name.ToString();
log.Information("[MSQProgression] Character: " + characterName + " @ " + worldName);
_ = player.Name.TextValue;
player.HomeWorld.Value.Name.ToString();
ExcelSheet<Quest> questSheet = dataManager.GetExcelSheet<Quest>();
if (questSheet == null)
{
log.Error("[MSQProgression] Failed to load Quest sheet!");
return;
}
List<Quest> completedMSQQuests = new List<Quest>();
log.Information("[MSQProgression] Checking MSQ quest completion...");
foreach (Quest quest in questSheet)
{
if (((ReadOnlySpan<uint>)MSQ_JOURNAL_GENRE_IDS).Contains(quest.JournalGenre.RowId) && QuestManager.IsQuestComplete((ushort)quest.RowId))
@ -543,60 +472,39 @@ public class MSQProgressionService
completedMSQQuests.Add(quest);
}
}
log.Information($"[MSQProgression] Character has {completedMSQQuests.Count} completed MSQ quests");
if (completedMSQQuests.Count == 0)
{
log.Warning("[MSQProgression] No completed MSQ quests found!");
return;
}
Quest latestMSQQuest = completedMSQQuests.OrderByDescending((Quest quest2) => quest2.RowId).First();
log.Information($"[MSQProgression] Latest completed MSQ quest ID: {latestMSQQuest.RowId}");
Quest questData = latestMSQQuest;
log.Information("[MSQProgression] === LATEST MSQ QUEST DETAILS ===");
log.Information($"[MSQProgression] Quest ID: {questData.RowId}");
log.Information($"[MSQProgression] Quest Name: {questData.Name}");
log.Information($"[MSQProgression] JournalGenre.RowId: {questData.JournalGenre.RowId}");
Quest questData = completedMSQQuests.OrderByDescending((Quest q) => q.RowId).First();
try
{
string genreName = questData.JournalGenre.Value.Name.ToString();
log.Information("[MSQProgression] JournalGenre.Name: " + genreName);
questData.JournalGenre.Value.Name.ToString();
}
catch
{
log.Information("[MSQProgression] JournalGenre.Name: ERROR");
}
log.Information($"[MSQProgression] Expansion.RowId: {questData.Expansion.RowId}");
try
{
string expansionName = questData.Expansion.Value.Name.ToString();
log.Information("[MSQProgression] Expansion.Name: " + expansionName);
questData.Expansion.Value.Name.ToString();
}
catch
{
log.Information("[MSQProgression] Expansion.Name: ERROR");
}
log.Information("[MSQProgression] === CHARACTER IS IN THIS EXPANSION ===");
log.Information($"[MSQProgression] Character is at Quest {questData.RowId} which is in:");
log.Information($"[MSQProgression] - JournalGenre: {questData.JournalGenre.RowId}");
log.Information($"[MSQProgression] - Expansion: {questData.Expansion.RowId}");
log.Information("[MSQProgression] === RECENT COMPLETED MSQ QUESTS (Last 10) ===");
foreach (Quest q in completedMSQQuests.OrderByDescending((Quest quest2) => quest2.RowId).Take(10).ToList())
foreach (Quest item in completedMSQQuests.OrderByDescending((Quest q) => q.RowId).Take(10).ToList())
{
log.Information($"[MSQProgression] Quest {q.RowId}: {q.Name} (Genre: {q.JournalGenre.RowId}, Exp: {q.Expansion.RowId})");
_ = item;
}
log.Information("[MSQProgression] === COMPLETED MSQ QUESTS BY EXPANSION ===");
foreach (IGrouping<MSQExpansionData.Expansion, Quest> group in from quest2 in completedMSQQuests
group quest2 by JournalGenreToExpansion.GetValueOrDefault(quest2.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn) into g
foreach (IGrouping<MSQExpansionData.Expansion, Quest> item2 in from q in completedMSQQuests
group q by JournalGenreToExpansion.GetValueOrDefault(q.JournalGenre.RowId, MSQExpansionData.Expansion.ARealmReborn) into g
orderby g.Key
select g)
{
log.Information($"[MSQProgression] {group.Key}: {group.Count()} quests completed");
_ = item2;
}
}
catch (Exception ex)
catch (Exception)
{
log.Error("[MSQProgression] ERROR in DebugCurrentCharacterQuest: " + ex.Message);
log.Error("[MSQProgression] Stack trace: " + ex.StackTrace);
}
}
}

View file

@ -48,6 +48,8 @@ public class QuestRotationExecutionService : IDisposable
private StepsOfFaithHandler? stepsOfFaithHandler;
private ErrorRecoveryService? errorRecoveryService;
private readonly List<StopPoint> stopPoints = new List<StopPoint>();
private RotationState currentState = new RotationState();
@ -64,10 +66,6 @@ public class QuestRotationExecutionService : IDisposable
private bool waitingForQuestAcceptForSubmarines;
private uint? lastDungeonQuestId;
private int? lastDungeonSequence;
private uint? lastSoloDutyQuestId;
private const double CharacterLoginTimeoutSeconds = 60.0;
@ -480,6 +478,12 @@ public class QuestRotationExecutionService : IDisposable
log.Information("[QuestRotation] Steps of Faith Handler service linked");
}
public void SetErrorRecoveryService(ErrorRecoveryService service)
{
errorRecoveryService = service;
log.Information("[QuestRotation] Error Recovery service linked");
}
private void MarkQuestCompleted(uint questId, string characterName)
{
if (!questCompletionByCharacter.ContainsKey(questId))
@ -626,6 +630,24 @@ public class QuestRotationExecutionService : IDisposable
return;
}
lastCheckTime = now;
if (isRotationActive && errorRecoveryService != null && errorRecoveryService.IsErrorDisconnect)
{
string charToRelog = errorRecoveryService.LastDisconnectedCharacter ?? currentState.CurrentCharacter;
if (!string.IsNullOrEmpty(charToRelog))
{
log.Warning("[ErrorRecovery] Disconnect detected for " + charToRelog);
log.Information("[ErrorRecovery] Automatically relogging to " + charToRelog + "...");
errorRecoveryService.Reset();
currentState.Phase = RotationPhase.WaitingForCharacterLogin;
currentState.CurrentCharacter = charToRelog;
currentState.PhaseStartTime = DateTime.Now;
autoRetainerIpc.SwitchCharacter(charToRelog);
log.Information("[ErrorRecovery] Relog initiated for " + charToRelog);
return;
}
log.Warning("[ErrorRecovery] Disconnect detected but no character to relog to");
errorRecoveryService.Reset();
}
if (deathHandler != null && combatDutyDetection != null && !combatDutyDetection.IsInDuty)
{
deathHandler.Update();
@ -1037,7 +1059,19 @@ public class QuestRotationExecutionService : IDisposable
private unsafe void HandleQuestMonitoring()
{
uint questId = currentState.CurrentStopQuestId;
if (!submarineManager.IsSubmarinePaused && !submarineManager.IsSubmarineCooldownActive())
bool hasReachedStopSequence = false;
StopPoint stopPointForSubmarine = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == questId && sp.IsActive);
if (stopPointForSubmarine != null && stopPointForSubmarine.Sequence.HasValue)
{
string currentQuestIdStr = questionableIPC.GetCurrentQuestId();
byte? currentSequence = questionableIPC.GetCurrentSequence();
if (!string.IsNullOrEmpty(currentQuestIdStr) && currentSequence.HasValue && uint.TryParse(currentQuestIdStr, out var currentQuestId) && currentQuestId == questId && currentSequence.Value >= stopPointForSubmarine.Sequence.Value)
{
hasReachedStopSequence = true;
log.Debug($"[QuestRotation] Stop sequence reached (Quest {questId} Seq {currentSequence.Value} >= {stopPointForSubmarine.Sequence.Value}) - skipping submarine check");
}
}
if (!hasReachedStopSequence && !submarineManager.IsSubmarinePaused && !submarineManager.IsSubmarineCooldownActive())
{
TimeSpan submarineCheckInterval = TimeSpan.FromSeconds(configuration.SubmarineCheckInterval);
if (DateTime.Now - lastSubmarineCheckTime >= submarineCheckInterval)
@ -1128,16 +1162,16 @@ public class QuestRotationExecutionService : IDisposable
bool shouldRotate = false;
if (activeStopPoint != null && activeStopPoint.Sequence.HasValue)
{
string currentQuestIdStr = questionableIPC.GetCurrentQuestId();
byte? currentSequence = questionableIPC.GetCurrentSequence();
if (!string.IsNullOrEmpty(currentQuestIdStr) && currentSequence.HasValue && uint.TryParse(currentQuestIdStr, out var currentQuestId))
string currentQuestIdStr2 = questionableIPC.GetCurrentQuestId();
byte? currentSequence2 = questionableIPC.GetCurrentSequence();
if (!string.IsNullOrEmpty(currentQuestIdStr2) && currentSequence2.HasValue && uint.TryParse(currentQuestIdStr2, out var currentQuestId2))
{
if (currentQuestId == questId)
if (currentQuestId2 == questId)
{
if (currentSequence.Value >= activeStopPoint.Sequence.Value)
if (currentSequence2.Value >= activeStopPoint.Sequence.Value)
{
log.Information($"[QuestRotation] ✓ Quest {questId} Sequence {activeStopPoint.Sequence.Value} reached by {currentState.CurrentCharacter}!");
log.Information($"[QuestRotation] Current Sequence: {currentSequence.Value} (reached {activeStopPoint.Sequence.Value})");
log.Information($"[QuestRotation] Current Sequence: {currentSequence2.Value} (reached {activeStopPoint.Sequence.Value})");
shouldRotate = true;
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Plugin.Services;
using Newtonsoft.Json.Linq;
@ -228,8 +229,33 @@ public class SubmarineManager : IDisposable
try
{
JObject json = JObject.Parse(jsonContent);
HashSet<string> enabledSubs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (json.SelectTokens("$..EnabledSubs").FirstOrDefault() is JArray enabledSubsArray)
{
foreach (JToken item in enabledSubsArray)
{
string subName = item.Value<string>();
if (!string.IsNullOrEmpty(subName))
{
enabledSubs.Add(subName);
}
}
if (enabledSubs.Count > 0)
{
log.Information($"[SubmarineManager] Found {enabledSubs.Count} enabled submarines: {string.Join(", ", enabledSubs)}");
}
else
{
log.Information("[SubmarineManager] EnabledSubs array found but empty - NO submarines will be checked");
}
FindReturnTimes(json, returnTimes, enabledSubs);
}
else
{
log.Information("[SubmarineManager] No EnabledSubs found in config - checking all submarines");
FindReturnTimes(json, returnTimes);
}
}
catch
{
string pattern = "\"ReturnTime\"\\s*:\\s*(\\d+)";
@ -244,21 +270,27 @@ public class SubmarineManager : IDisposable
return returnTimes;
}
private void FindReturnTimes(JToken token, List<long> returnTimes)
private void FindReturnTimes(JToken token, List<long> returnTimes, HashSet<string>? enabledSubs = null)
{
if (token is JObject obj)
{
if (obj.TryGetValue("Name", out JToken nameToken) && obj.TryGetValue("ReturnTime", out JToken returnTimeToken))
{
string submarineName = nameToken.Value<string>();
if ((enabledSubs == null || (submarineName != null && enabledSubs.Contains(submarineName))) && returnTimeToken.Type == JTokenType.Integer)
{
long returnTime = returnTimeToken.Value<long>();
returnTimes.Add(returnTime);
if (enabledSubs != null)
{
log.Debug($"[SubmarineManager] Including submarine '{submarineName}' (ReturnTime: {returnTime})");
}
}
}
{
foreach (JProperty property in obj.Properties())
{
if (property.Name == "ReturnTime" && property.Value.Type == JTokenType.Integer)
{
returnTimes.Add(property.Value.Value<long>());
}
else
{
FindReturnTimes(property.Value, returnTimes);
}
FindReturnTimes(property.Value, returnTimes, enabledSubs);
}
return;
}
@ -269,7 +301,7 @@ public class SubmarineManager : IDisposable
}
foreach (JToken item in array)
{
FindReturnTimes(item, returnTimes);
FindReturnTimes(item, returnTimes, enabledSubs);
}
}

View file

@ -50,6 +50,8 @@ public class NewMainWindow : Window, IDisposable
private readonly MSQProgressionService msqProgressionService;
private readonly Configuration configuration;
private readonly IPluginLog log;
private readonly IUiBuilder uiBuilder;
@ -128,12 +130,6 @@ public class NewMainWindow : Window, IDisposable
private string selectedWorldFilter = "All";
private bool showWorldActionDropdown;
private uint inputStopQuestId;
private int inputStopSequence = -1;
private string selectedEventQuestId = "";
private List<(string QuestId, string QuestName)> availableEventQuests = new List<(string, string)>();
@ -314,8 +310,8 @@ public class NewMainWindow : Window, IDisposable
}
}
public NewMainWindow(Plugin plugin, AutoRetainerIPC autoRetainerIpc, QuestTrackingService questTrackingService, QuestRotationExecutionService questRotationService, EventQuestExecutionService eventQuestService, AlliedSocietyRotationService alliedSocietyRotationService, AlliedSocietyPriorityWindow alliedSocietyPriorityWindow, DataCenterService dataCenterService, MSQProgressionService msqProgressionService, IPluginLog log, IUiBuilder uiBuilder, IDataManager dataManager)
: base("Questionable Companion##NewMainWindow", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)
public NewMainWindow(Plugin plugin, AutoRetainerIPC autoRetainerIpc, QuestTrackingService questTrackingService, QuestRotationExecutionService questRotationService, EventQuestExecutionService eventQuestService, AlliedSocietyRotationService alliedSocietyRotationService, AlliedSocietyPriorityWindow alliedSocietyPriorityWindow, DataCenterService dataCenterService, MSQProgressionService msqProgressionService, Configuration configuration, IPluginLog log, IUiBuilder uiBuilder, IDataManager dataManager)
: base("Questionable Companion##NewMainWindow", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoBackground)
{
this.plugin = plugin;
this.autoRetainerIpc = autoRetainerIpc;
@ -326,6 +322,7 @@ public class NewMainWindow : Window, IDisposable
this.alliedSocietyPriorityWindow = alliedSocietyPriorityWindow;
this.dataCenterService = dataCenterService;
this.msqProgressionService = msqProgressionService;
this.configuration = configuration;
this.log = log;
this.uiBuilder = uiBuilder;
this.dataManager = dataManager;
@ -431,7 +428,6 @@ public class NewMainWindow : Window, IDisposable
if (registeredCharacters.Count > 0)
{
initialCharacterLoadComplete = true;
log.Information("[NewMainWindow] ✅ Initial character load successful");
}
charactersByDataCenter = dataCenterService.GroupCharactersByDataCenter(registeredCharacters);
availableWorlds = (from w in (from c in registeredCharacters
@ -447,6 +443,13 @@ public class NewMainWindow : Window, IDisposable
characterSelection[character] = false;
}
}
foreach (string savedChar in configuration.SelectedCharactersForUI)
{
if (characterSelection.ContainsKey(savedChar))
{
characterSelection[savedChar] = true;
}
}
}
catch (Exception ex)
{
@ -613,7 +616,7 @@ public class NewMainWindow : Window, IDisposable
uint rightColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorSecondary.X * 0.3f, colorSecondary.Y * 0.3f, colorSecondary.Z * 0.3f, 1f));
drawList.AddRectFilledMultiColor(windowPos, windowPos + new Vector2(windowSize.X, height), leftColor, rightColor, rightColor, leftColor);
Vector2 titlePos = windowPos + new Vector2(10f, 7f);
drawList.AddText(titlePos, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)), "Questionable Companion V.1.0.4");
drawList.AddText(titlePos, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)), "Questionable Companion V.1.0.5");
Vector2 minimizeButtonPos = windowPos + new Vector2(windowSize.X - 60f, 3f);
Vector2 minimizeButtonSize = new Vector2(24f, 24f);
if (ImGui.IsMouseHoveringRect(minimizeButtonPos, minimizeButtonPos + minimizeButtonSize))
@ -624,7 +627,22 @@ public class NewMainWindow : Window, IDisposable
isMinimized = !isMinimized;
}
}
drawList.AddText(minimizeButtonPos + new Vector2(8f, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), "_");
uint arrowColor = ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f));
Vector2 arrowCenter = minimizeButtonPos + new Vector2(12f, 12f);
if (isMinimized)
{
Vector2 p1 = arrowCenter + new Vector2(-4f, -5f);
Vector2 p2 = arrowCenter + new Vector2(-4f, 5f);
Vector2 p3 = arrowCenter + new Vector2(4f, 0f);
drawList.AddTriangleFilled(p1, p2, p3, arrowColor);
}
else
{
Vector2 p4 = arrowCenter + new Vector2(-5f, -4f);
Vector2 p5 = arrowCenter + new Vector2(5f, -4f);
Vector2 p6 = arrowCenter + new Vector2(0f, 4f);
drawList.AddTriangleFilled(p4, p5, p6, arrowColor);
}
Vector2 closeButtonPos = windowPos + new Vector2(windowSize.X - 30f, 3f);
Vector2 closeButtonSize = new Vector2(24f, 24f);
if (ImGui.IsMouseHoveringRect(closeButtonPos, closeButtonPos + closeButtonSize))
@ -1909,6 +1927,10 @@ public class NewMainWindow : Window, IDisposable
{
characterSelection[character] = true;
}
configuration.SelectedCharactersForUI = (from kvp in characterSelection
where kvp.Value
select kvp.Key).ToList();
configuration.Save();
}
ImGui.SameLine();
if (ImGui.Button("Deselect All"))
@ -1917,6 +1939,10 @@ public class NewMainWindow : Window, IDisposable
{
characterSelection[character2] = false;
}
configuration.SelectedCharactersForUI = (from kvp in characterSelection
where kvp.Value
select kvp.Key).ToList();
configuration.Save();
}
ImGui.SameLine();
ImGui.SetNextItemWidth(150f);
@ -1967,6 +1993,10 @@ public class NewMainWindow : Window, IDisposable
if (ImGui.Checkbox("##Select", ref isSelected))
{
characterSelection[character3] = isSelected;
configuration.SelectedCharactersForUI = (from kvp in characterSelection
where kvp.Value
select kvp.Key).ToList();
configuration.Save();
}
}
ImGui.SetCursorScreenPos(cursorPos + new Vector2(40f, 8f));

View file

@ -42,6 +42,8 @@ public class Configuration : IPluginConfiguration
public List<string> SelectedCharactersForRotation { get; set; } = new List<string>();
public List<string> SelectedCharactersForUI { get; set; } = new List<string>();
public Dictionary<uint, List<string>> QuestCompletionByCharacter { get; set; } = new Dictionary<uint, List<string>>();
public Dictionary<string, List<string>> EventQuestCompletionByCharacter { get; set; } = new Dictionary<string, List<string>>();

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Command;
@ -126,6 +127,8 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
private AlliedSocietyPriorityWindow AlliedSocietyPriorityWindow { get; init; }
private ErrorRecoveryService ErrorRecoveryService { get; init; }
private ConfigWindow ConfigWindow { get; init; }
private NewMainWindow NewMainWindow { get; init; }
@ -182,7 +185,7 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
Log.Debug("[Plugin] Initializing StepsOfFaithHandler...");
StepsOfFaithHandler = new StepsOfFaithHandler(Condition, Log, ClientState, CommandManager, Framework, Configuration);
Log.Debug("[Plugin] Initializing MSQProgressionService...");
MSQProgressionService = new MSQProgressionService(DataManager, Log, QuestDetection, ClientState, Framework);
MSQProgressionService = new MSQProgressionService(DataManager, Log, QuestDetection, ObjectTable, Framework);
Log.Debug("[Plugin] Initializing ChauffeurMode...");
ChauffeurMode = new ChauffeurModeService(Configuration, Log, ClientState, Condition, Framework, CommandManager, DataManager, PartyList, ObjectTable, QuestionableIPC, CrossProcessIPC, PartyInviteService, PartyInviteAutoAccept, PluginInterface, MemoryHelper, MovementMonitor);
MovementMonitor.SetChauffeurMode(ChauffeurMode);
@ -194,6 +197,9 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
AlliedSocietyQuestSelector = new AlliedSocietyQuestSelector(QuestionableIPC, Log);
AlliedSocietyRotationService = new AlliedSocietyRotationService(QuestionableIPC, AlliedSocietyDatabase, AlliedSocietyQuestSelector, AutoRetainerIPC, Configuration, Log, Framework, CommandManager, Condition, ClientState);
AlliedSocietyPriorityWindow = new AlliedSocietyPriorityWindow(Configuration, AlliedSocietyDatabase);
Log.Debug("[Plugin] Initializing Error Recovery Service...");
ErrorRecoveryService = new ErrorRecoveryService(Log, GameInterop, ClientState, AutoRetainerIPC, Framework, GameGui);
QuestRotationService.SetErrorRecoveryService(ErrorRecoveryService);
MultiClientIPC.OnChatMessageReceived += OnMultiClientChatReceived;
CrossProcessIPC.OnChatMessageReceived += OnMultiClientChatReceived;
CrossProcessIPC.OnCommandReceived += OnCommandReceived;
@ -218,7 +224,7 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
}
Log.Debug("[Plugin] Initializing windows...");
ConfigWindow = new ConfigWindow(this);
NewMainWindow = new NewMainWindow(this, AutoRetainerIPC, QuestTrackingService, QuestRotationService, EventQuestService, AlliedSocietyRotationService, AlliedSocietyPriorityWindow, dataCenterService, MSQProgressionService, Log, PluginInterface.UiBuilder, DataManager);
NewMainWindow = new NewMainWindow(this, AutoRetainerIPC, QuestTrackingService, QuestRotationService, EventQuestService, AlliedSocietyRotationService, AlliedSocietyPriorityWindow, dataCenterService, MSQProgressionService, Configuration, Log, PluginInterface.UiBuilder, DataManager);
DebugWindow = new DebugWindow(this, CombatDutyDetection, DeathHandler, DungeonAutomation);
WindowSystem.AddWindow(ConfigWindow);
WindowSystem.AddWindow(NewMainWindow);
@ -228,6 +234,14 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
{
HelpMessage = "Open the Quest Sequence Manager"
});
string[] array = new string[2] { "/qstc", "/qstcompanion" };
foreach (string alias in array)
{
CommandManager.AddHandler(alias, new CommandInfo(OnCommand)
{
HelpMessage = "Open the Quest Sequence Manager"
});
}
CommandManager.AddHandler("/qsthelper", new CommandInfo(OnHelperCommand)
{
HelpMessage = "Helper commands: /qsthelper reset - Reset helper status to Available"
@ -393,6 +407,7 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
LifestreamIPC?.Dispose();
AutoRetainerIPC?.Dispose();
QuestionableIPC?.Dispose();
ErrorRecoveryService?.Dispose();
Log.Debug("[Plugin] Services disposed");
}
catch (Exception ex3)
@ -418,6 +433,11 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
{
CommandManager.RemoveHandler("/qstcomp");
CommandManager.RemoveHandler("/qsthelper");
string[] array = new string[2] { "/qstc", "/qstcompanion" };
foreach (string alias in array)
{
CommandManager.RemoveHandler(alias);
}
Log.Debug("[Plugin] Command handlers removed");
}
catch (Exception ex5)
@ -487,6 +507,9 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
case "mounts":
TestListMounts();
break;
case "aetheryte":
TestFindNearestAetheryte();
break;
default:
NewMainWindow.Toggle();
break;
@ -802,6 +825,85 @@ public sealed class Plugin : IDalamudPlugin, IDisposable
Log.Information("========================================");
}
private void TestFindNearestAetheryte()
{
Log.Information("========================================");
Log.Information("[TEST] Finding Nearest Aetheryte (Map Data)");
Log.Information("========================================");
IPlayerCharacter player = ClientState.LocalPlayer;
if (player == null)
{
Log.Error("[TEST] Player not logged in!");
Log.Information("========================================");
return;
}
Vector3 playerPos = player.Position;
ushort territoryId = ClientState.TerritoryType;
Log.Information($"[TEST] Player Position: ({playerPos.X:F2}, {playerPos.Y:F2}, {playerPos.Z:F2})");
Log.Information($"[TEST] Territory ID: {territoryId}");
ExcelSheet<Aetheryte> aetheryteSheet = DataManager.GetExcelSheet<Aetheryte>();
ExcelSheet<Map> mapSheet = DataManager.GetExcelSheet<Map>();
if (aetheryteSheet == null || mapSheet == null)
{
Log.Error("[TEST] Failed to load sheets!");
Log.Information("========================================");
return;
}
float minDistance = float.MaxValue;
string nearestName = "Unknown";
uint nearestId = 0u;
int aetheryteCount = 0;
foreach (Aetheryte aetheryte in aetheryteSheet)
{
if (aetheryte.Territory.RowId != territoryId || !aetheryte.IsAetheryte)
{
continue;
}
aetheryteCount++;
PlaceName? placeName = aetheryte.PlaceName.ValueNullable;
string nameStr = (placeName.HasValue ? placeName.Value.Name.ExtractText() : $"Aetheryte #{aetheryte.RowId}");
Map? map = aetheryte.Map.ValueNullable;
if (map.HasValue)
{
float mapX = ConvertMapCoordinateToRawPosition(aetheryte.AetherstreamX, map.Value.SizeFactor);
float mapY = ConvertMapCoordinateToRawPosition(aetheryte.AetherstreamY, map.Value.SizeFactor);
Vector3 aethPos = new Vector3(mapX, 0f, mapY);
float num = playerPos.X - aethPos.X;
float dz = playerPos.Z - aethPos.Z;
float distance = (float)Math.Sqrt(num * num + dz * dz);
Log.Information($"[TEST] Aetheryte #{aetheryteCount}: {nameStr}");
Log.Information($"[TEST] Position: ({aethPos.X:F2}, ?, {aethPos.Z:F2})");
Log.Information($"[TEST] Distance (2D): {distance:F2} yalms");
Log.Information($"[TEST] Aetheryte ID: {aetheryte.RowId}");
if (distance < minDistance)
{
minDistance = distance;
nearestName = nameStr;
nearestId = aetheryte.RowId;
}
}
}
if (aetheryteCount == 0)
{
Log.Warning("[TEST] No aetherytes found in this territory!");
}
else
{
Log.Information("========================================");
Log.Information("[TEST] NEAREST AETHERYTE: " + nearestName);
Log.Information($"[TEST] Distance (2D): {minDistance:F2} yalms");
Log.Information($"[TEST] Aetheryte ID: {nearestId}");
Log.Information("========================================");
}
Log.Information("========================================");
}
private float ConvertMapCoordinateToRawPosition(int coordinate, ushort scale)
{
float c = (float)(int)scale / 100f;
return ((((float)coordinate - 1024f) / 2048f + 1f) * c / 2f - 1f) * (2048f / c) * 1000f / 2048f;
}
private void TestAlliedSociety()
{
Log.Information("========================================");