qstbak/QuestionableCompanion/QuestionableCompanion.Services/QuestDetectionService.cs
2025-12-04 04:39:08 +10:00

256 lines
6 KiB
C#

using System;
using System.Collections.Generic;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game;
namespace QuestionableCompanion.Services;
public class QuestDetectionService : IDisposable
{
private readonly IFramework framework;
private readonly IPluginLog log;
private readonly IClientState clientState;
private readonly HashSet<uint> acceptedQuests = new HashSet<uint>();
private readonly HashSet<uint> completedQuests = new HashSet<uint>();
private HashSet<uint> completedQuestCache = new HashSet<uint>();
private DateTime lastCacheRefresh = DateTime.MinValue;
private const int CACHE_REFRESH_MINUTES = 5;
public event Action<uint, string>? QuestAccepted;
public event Action<uint, string>? QuestCompleted;
public QuestDetectionService(IFramework framework, IPluginLog log, IClientState clientState)
{
this.framework = framework;
this.log = log;
this.clientState = clientState;
framework.Update += OnFrameworkUpdate;
log.Information("[QuestDetection] Service initialized");
}
private void OnFrameworkUpdate(IFramework framework)
{
if (!clientState.IsLoggedIn)
{
return;
}
try
{
CheckQuestUpdates();
}
catch (Exception ex)
{
log.Debug("[QuestDetection] Error in framework update: " + ex.Message);
}
}
private unsafe void CheckQuestUpdates()
{
QuestManager* questManager = QuestManager.Instance();
if (questManager == null)
{
log.Debug("[QuestDetection] QuestManager instance is null");
return;
}
try
{
Span<QuestWork> normalQuests = questManager->NormalQuests;
if (normalQuests.Length == 0)
{
log.Debug("[QuestDetection] NormalQuests array is empty");
return;
}
int maxSlots = Math.Min(normalQuests.Length, 30);
for (int i = 0; i < maxSlots; i++)
{
try
{
QuestWork quest = normalQuests[i];
if (quest.QuestId == 0)
{
continue;
}
uint questId = quest.QuestId;
if (!acceptedQuests.Contains(questId))
{
if (!IsQuestComplete(questId))
{
acceptedQuests.Add(questId);
string questName = GetQuestName(questId);
log.Information($"[QuestDetection] Quest Accepted: {questId} - {questName}");
this.QuestAccepted?.Invoke(questId, questName);
}
}
else if (!completedQuests.Contains(questId) && IsQuestComplete(questId))
{
completedQuests.Add(questId);
string questName2 = GetQuestName(questId);
log.Information($"[QuestDetection] Quest Completed: {questId} - {questName2}");
this.QuestCompleted?.Invoke(questId, questName2);
}
}
catch (IndexOutOfRangeException)
{
log.Debug($"[QuestDetection] Index {i} out of range, stopping quest check");
break;
}
catch (Exception ex2)
{
log.Debug($"[QuestDetection] Error checking quest slot {i}: {ex2.Message}");
}
}
}
catch (Exception ex3)
{
log.Warning("[QuestDetection] Error accessing quest data: " + ex3.Message);
}
}
private bool IsQuestComplete(uint questId)
{
try
{
return QuestManager.IsQuestComplete(questId);
}
catch
{
return false;
}
}
public unsafe bool IsQuestCompletedDirect(uint questId)
{
try
{
if (QuestManager.Instance() == null)
{
log.Warning("[QuestDetection] QuestManager instance not available");
return false;
}
bool isComplete = QuestManager.IsQuestComplete(questId);
log.Debug($"[QuestDetection] Quest {questId} completion status: {isComplete}");
return isComplete;
}
catch (Exception ex)
{
log.Error($"[QuestDetection] Failed to check quest {questId}: {ex.Message}");
return false;
}
}
public unsafe List<uint> GetAllCompletedQuestIds()
{
List<uint> completed = new List<uint>();
try
{
if (QuestManager.Instance() == null)
{
log.Warning("[QuestDetection] QuestManager instance not available");
return completed;
}
log.Information("[QuestDetection] Scanning for completed quests...");
foreach (var item in new List<(uint, uint)>
{
(1u, 3000u),
(65000u, 71000u)
})
{
uint start = item.Item1;
uint end = item.Item2;
for (uint i = start; i <= end; i++)
{
try
{
if (QuestManager.IsQuestComplete(i))
{
completed.Add(i);
}
}
catch
{
}
}
}
log.Information($"[QuestDetection] Retrieved {completed.Count} completed quests");
}
catch (Exception ex)
{
log.Error("[QuestDetection] Error while fetching completed quests: " + ex.Message);
}
return completed;
}
public void RefreshQuestCache()
{
try
{
log.Information("[QuestDetection] Refreshing quest cache...");
List<uint> allCompleted = GetAllCompletedQuestIds();
completedQuestCache = new HashSet<uint>(allCompleted);
lastCacheRefresh = DateTime.Now;
log.Information($"[QuestDetection] Quest cache refreshed with {completedQuestCache.Count} completed quests");
}
catch (Exception ex)
{
log.Error("[QuestDetection] Failed to refresh quest cache: " + ex.Message);
}
}
public bool IsQuestCompletedCached(uint questId)
{
if (completedQuestCache.Count == 0 || (DateTime.Now - lastCacheRefresh).TotalMinutes > 5.0)
{
RefreshQuestCache();
}
return completedQuestCache.Contains(questId);
}
private string GetQuestName(uint questId)
{
try
{
return $"Quest {questId}";
}
catch
{
return $"Quest {questId}";
}
}
public void ResetTracking()
{
acceptedQuests.Clear();
completedQuests.Clear();
completedQuestCache.Clear();
lastCacheRefresh = DateTime.MinValue;
log.Information("[QuestDetection] Tracking reset");
}
public bool IsQuestAccepted(uint questId)
{
return acceptedQuests.Contains(questId);
}
public bool IsQuestCompleted(uint questId)
{
return completedQuests.Contains(questId);
}
public void Dispose()
{
framework.Update -= OnFrameworkUpdate;
acceptedQuests.Clear();
completedQuests.Clear();
completedQuestCache.Clear();
log.Information("[QuestDetection] Service disposed");
}
}