forked from aly/qstbak
qstcompanion v1.0.1
This commit is contained in:
parent
3e10cbbbf2
commit
44c67ab71b
79 changed files with 21148 additions and 0 deletions
|
|
@ -0,0 +1,301 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace QuestionableCompanion.Services;
|
||||
|
||||
public class QuestPreCheckService : IDisposable
|
||||
{
|
||||
private readonly IPluginLog log;
|
||||
|
||||
private readonly IClientState clientState;
|
||||
|
||||
private readonly Configuration config;
|
||||
|
||||
private readonly AutoRetainerIPC autoRetainerIPC;
|
||||
|
||||
private Dictionary<string, bool> preCheckResults = new Dictionary<string, bool>();
|
||||
|
||||
private Dictionary<string, Dictionary<uint, bool>> questDatabase = new Dictionary<string, Dictionary<uint, bool>>();
|
||||
|
||||
private Dictionary<string, DateTime> lastRefreshByCharacter = new Dictionary<string, DateTime>();
|
||||
|
||||
private readonly TimeSpan refreshInterval = TimeSpan.FromMinutes(30L);
|
||||
|
||||
private string QuestDatabasePath
|
||||
{
|
||||
get
|
||||
{
|
||||
global::_003C_003Ey__InlineArray5<string> buffer = default(global::_003C_003Ey__InlineArray5<string>);
|
||||
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<global::_003C_003Ey__InlineArray5<string>, string>(ref buffer, 0) = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<global::_003C_003Ey__InlineArray5<string>, string>(ref buffer, 1) = "XIVLauncher";
|
||||
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<global::_003C_003Ey__InlineArray5<string>, string>(ref buffer, 2) = "pluginConfigs";
|
||||
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<global::_003C_003Ey__InlineArray5<string>, string>(ref buffer, 3) = "QuestionableCompanion";
|
||||
global::_003CPrivateImplementationDetails_003E.InlineArrayElementRef<global::_003C_003Ey__InlineArray5<string>, string>(ref buffer, 4) = "QuestDatabase.json";
|
||||
return Path.Combine(global::_003CPrivateImplementationDetails_003E.InlineArrayAsReadOnlySpan<global::_003C_003Ey__InlineArray5<string>, string>(in buffer, 5));
|
||||
}
|
||||
}
|
||||
|
||||
public QuestPreCheckService(IPluginLog log, IClientState clientState, Configuration config, AutoRetainerIPC autoRetainerIPC)
|
||||
{
|
||||
this.log = log;
|
||||
this.clientState = clientState;
|
||||
this.config = config;
|
||||
this.autoRetainerIPC = autoRetainerIPC;
|
||||
LoadQuestDatabase();
|
||||
}
|
||||
|
||||
private void LoadQuestDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureQuestDatabasePath();
|
||||
if (!File.Exists(QuestDatabasePath))
|
||||
{
|
||||
log.Information("[QuestPreCheck] Creating new quest database...");
|
||||
questDatabase = new Dictionary<string, Dictionary<uint, bool>>();
|
||||
return;
|
||||
}
|
||||
string json = File.ReadAllText(QuestDatabasePath);
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
questDatabase = new Dictionary<string, Dictionary<uint, bool>>();
|
||||
return;
|
||||
}
|
||||
questDatabase = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<uint, bool>>>(json) ?? new Dictionary<string, Dictionary<uint, bool>>();
|
||||
log.Information($"[QuestPreCheck] Loaded quest database for {questDatabase.Count} characters");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("[QuestPreCheck] Error loading quest database: " + ex.Message);
|
||||
questDatabase = new Dictionary<string, Dictionary<uint, bool>>();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveQuestDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureQuestDatabasePath();
|
||||
string json = JsonConvert.SerializeObject(questDatabase, Formatting.Indented);
|
||||
File.WriteAllText(QuestDatabasePath, json);
|
||||
log.Information($"[QuestPreCheck] Quest database saved ({questDatabase.Count} characters)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("[QuestPreCheck] Error saving quest database: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureQuestDatabasePath()
|
||||
{
|
||||
string directory = Path.GetDirectoryName(QuestDatabasePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void ScanCurrentCharacterQuestStatus(bool verbose = false)
|
||||
{
|
||||
if (clientState.LocalPlayer == null)
|
||||
{
|
||||
log.Warning("[QuestPreCheck] No local player found");
|
||||
return;
|
||||
}
|
||||
string worldName = clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
|
||||
string charName = $"{clientState.LocalPlayer.Name}@{worldName}";
|
||||
if (verbose)
|
||||
{
|
||||
log.Information("[QuestPreCheck] Scanning quest status for: " + charName);
|
||||
}
|
||||
if (!questDatabase.ContainsKey(charName))
|
||||
{
|
||||
questDatabase[charName] = new Dictionary<uint, bool>();
|
||||
}
|
||||
if (QuestManager.Instance() == null)
|
||||
{
|
||||
log.Error("[QuestPreCheck] QuestManager not available");
|
||||
return;
|
||||
}
|
||||
int questsScanned = 0;
|
||||
int questsCompleted = 0;
|
||||
int questsChanged = 0;
|
||||
List<uint> newlyCompleted = new List<uint>();
|
||||
List<uint> questsToScan = config.QuestPreCheckRange ?? new List<uint>();
|
||||
if (questsToScan.Count == 0)
|
||||
{
|
||||
for (uint questId = 1u; questId <= 4500; questId++)
|
||||
{
|
||||
questsToScan.Add(questId);
|
||||
}
|
||||
}
|
||||
foreach (uint questId2 in questsToScan)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool num = QuestManager.IsQuestComplete((ushort)(questId2 % 65536));
|
||||
questsScanned++;
|
||||
if (num)
|
||||
{
|
||||
questsCompleted++;
|
||||
if (!questDatabase[charName].GetValueOrDefault(questId2, defaultValue: false))
|
||||
{
|
||||
questDatabase[charName][questId2] = true;
|
||||
questsChanged++;
|
||||
newlyCompleted.Add(questId2);
|
||||
if (verbose)
|
||||
{
|
||||
log.Debug($"[QuestPreCheck] {charName} - Quest {questId2}: ✓ NEWLY COMPLETED");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
questDatabase[charName][questId2] = true;
|
||||
}
|
||||
}
|
||||
if (verbose && questId2 % 500 == 0)
|
||||
{
|
||||
log.Debug($"[QuestPreCheck] Progress: {questId2}/{questsToScan.Count} quests scanned...");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"[QuestPreCheck] Error checking quest {questId2}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
if (verbose)
|
||||
{
|
||||
log.Information($"[QuestPreCheck] Scan complete: {questsScanned} checked, {questsCompleted} completed, {questsChanged} changed");
|
||||
if (newlyCompleted.Count > 0)
|
||||
{
|
||||
log.Information("[QuestPreCheck] NEWLY COMPLETED: " + string.Join(", ", newlyCompleted));
|
||||
}
|
||||
}
|
||||
lastRefreshByCharacter[charName] = DateTime.Now;
|
||||
SaveQuestDatabase();
|
||||
}
|
||||
|
||||
public void RefreshQuestDatabasePeriodic()
|
||||
{
|
||||
if (clientState.LocalPlayer != null && clientState.IsLoggedIn)
|
||||
{
|
||||
string worldName = clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
|
||||
string charName = $"{clientState.LocalPlayer.Name}@{worldName}";
|
||||
if (!lastRefreshByCharacter.TryGetValue(charName, out var lastRefresh) || DateTime.Now - lastRefresh >= refreshInterval)
|
||||
{
|
||||
log.Information("[QuestDB] === 30-MINUTE REFRESH TRIGGERED ===");
|
||||
log.Information("[QuestDB] Updating quest status for: " + charName);
|
||||
ScanCurrentCharacterQuestStatus(verbose: true);
|
||||
log.Information("[QuestDB] === 30-MINUTE REFRESH COMPLETE ===");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogCompletedQuestsBeforeLogout()
|
||||
{
|
||||
if (clientState.LocalPlayer != null)
|
||||
{
|
||||
string worldName = clientState.LocalPlayer.HomeWorld.Value.Name.ToString();
|
||||
string charName = $"{clientState.LocalPlayer.Name}@{worldName}";
|
||||
log.Information("[QuestDB] Logging final quest status before logout: " + charName);
|
||||
ScanCurrentCharacterQuestStatus();
|
||||
log.Information("[QuestDB] Final quest state saved for: " + charName);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, bool> PerformPreRotationCheck(uint stopQuestId, List<string> characters)
|
||||
{
|
||||
log.Information("[QuestPreCheck] === STARTING PRE-ROTATION QUEST VERIFICATION ===");
|
||||
log.Information($"[QuestPreCheck] Checking {characters.Count} characters for quest {stopQuestId}...");
|
||||
preCheckResults.Clear();
|
||||
foreach (string character in characters)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (questDatabase.ContainsKey(character) && questDatabase[character].ContainsKey(stopQuestId))
|
||||
{
|
||||
bool isCompleted = questDatabase[character][stopQuestId];
|
||||
preCheckResults[character] = isCompleted;
|
||||
string status = (isCompleted ? "✓ COMPLETED" : "○ PENDING");
|
||||
log.Information($"[QuestPreCheck] {character}: {status} (from database)");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug("[QuestPreCheck] " + character + ": Not in database, will check during rotation");
|
||||
preCheckResults[character] = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("[QuestPreCheck] Error checking " + character + ": " + ex.Message);
|
||||
preCheckResults[character] = false;
|
||||
}
|
||||
}
|
||||
log.Information("[QuestPreCheck] === PRE-ROTATION CHECK COMPLETE ===");
|
||||
return preCheckResults;
|
||||
}
|
||||
|
||||
public bool ShouldSkipCharacter(string characterName, uint questId)
|
||||
{
|
||||
if (preCheckResults.TryGetValue(characterName, out var isCompleted) && isCompleted)
|
||||
{
|
||||
log.Information($"[QuestPreCheck] Character {characterName} already completed quest {questId} - SKIPPING");
|
||||
return true;
|
||||
}
|
||||
bool completed = default(bool);
|
||||
if (questDatabase.TryGetValue(characterName, out Dictionary<uint, bool> quests) && quests.TryGetValue(questId, out completed) && completed)
|
||||
{
|
||||
log.Information($"[QuestPreCheck] Character {characterName} already completed quest {questId} (from DB) - SKIPPING");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool? GetQuestStatus(string characterName, uint questId)
|
||||
{
|
||||
if (questDatabase.TryGetValue(characterName, out Dictionary<uint, bool> quests) && quests.TryGetValue(questId, out var isCompleted))
|
||||
{
|
||||
return isCompleted;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<uint> GetCompletedQuests(string characterName)
|
||||
{
|
||||
if (!questDatabase.TryGetValue(characterName, out Dictionary<uint, bool> quests))
|
||||
{
|
||||
return new List<uint>();
|
||||
}
|
||||
return (from kvp in quests
|
||||
where kvp.Value
|
||||
select kvp.Key).ToList();
|
||||
}
|
||||
|
||||
public void MarkQuestCompleted(string characterName, uint questId)
|
||||
{
|
||||
if (!questDatabase.ContainsKey(characterName))
|
||||
{
|
||||
questDatabase[characterName] = new Dictionary<uint, bool>();
|
||||
}
|
||||
questDatabase[characterName][questId] = true;
|
||||
SaveQuestDatabase();
|
||||
log.Information($"[QuestPreCheck] Marked quest {questId} as completed for {characterName}");
|
||||
}
|
||||
|
||||
public void ClearPreCheckResults()
|
||||
{
|
||||
preCheckResults.Clear();
|
||||
log.Information("[QuestPreCheck] Pre-check results cleared");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SaveQuestDatabase();
|
||||
log.Information("[QuestPreCheck] Service disposed");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue