qstbak/Questionable/Questionable.Controller/GatheringPointRegistry.cs
2026-08-17 20:29:32 +10:00

210 lines
7.2 KiB
C#

#define RELEASE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Dalamud.Plugin;
using LLib.GameData;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.GatheringPaths;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Validation;
using Questionable.Validation.Validators;
namespace Questionable.Controller;
internal sealed class GatheringPointRegistry
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly GatheringData _gatheringData;
private readonly GatheringSchemaValidator _gatheringSchemaValidator;
private readonly QuestValidator _questValidator;
private readonly ILogger<GatheringPointRegistry> _logger;
private volatile Dictionary<GatheringPointId, GatheringRoot> _gatheringPoints = new Dictionary<GatheringPointId, GatheringRoot>();
public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, GatheringData gatheringData, GatheringSchemaValidator gatheringSchemaValidator, QuestValidator questValidator, ILogger<GatheringPointRegistry> logger)
{
_pluginInterface = pluginInterface;
_gatheringData = gatheringData;
_gatheringSchemaValidator = gatheringSchemaValidator;
_questValidator = questValidator;
_logger = logger;
}
public Dictionary<GatheringPointId, GatheringRoot> Build()
{
Dictionary<GatheringPointId, GatheringRoot> dictionary = new Dictionary<GatheringPointId, GatheringRoot>();
_gatheringSchemaValidator.Reset();
LoadGatheringPointsFromAssembly(dictionary);
try
{
LoadFromDirectory(dictionary, new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "GatheringPoints")));
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to load gathering points from user directory (some may have been successfully loaded)");
}
foreach (ValidationIssue item in _gatheringSchemaValidator.Validate())
{
_questValidator.AddValidationIssue(item);
}
_logger.LogDebug("Loaded {Count} gathering points in total", dictionary.Count);
return dictionary;
}
public void Publish(Dictionary<GatheringPointId, GatheringRoot> gatheringPoints)
{
_gatheringPoints = gatheringPoints;
}
public void Reload()
{
Publish(Build());
}
[Conditional("RELEASE")]
private void LoadGatheringPointsFromAssembly(Dictionary<GatheringPointId, GatheringRoot> gatheringPoints)
{
foreach (var (value, value2) in AssemblyGatheringLocationLoader.GetLocations())
{
gatheringPoints[new GatheringPointId(value)] = value2;
}
_logger.LogDebug("Loaded {Count} gathering points from assembly", gatheringPoints.Count);
}
[Conditional("DEBUG")]
private void LoadGatheringPointsFromProjectDirectory(Dictionary<GatheringPointId, GatheringRoot> gatheringPoints)
{
DirectoryInfo directoryInfo = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
if (directoryInfo == null)
{
_logger.LogWarning("Could not determine solution directory from assembly location: {AssemblyLocation}", _pluginInterface.AssemblyLocation.FullName);
return;
}
DirectoryInfo directoryInfo2 = new DirectoryInfo(Path.Combine(directoryInfo.FullName, "Paths", "GatheringPaths"));
try
{
foreach (string value in ExpansionData.ExpansionFolders.Values)
{
LoadFromDirectory(gatheringPoints, new DirectoryInfo(Path.Combine(directoryInfo2.FullName, value)));
}
}
catch (Exception exception)
{
gatheringPoints.Clear();
_logger.LogError(exception, "Failed to load gathering points from project directory");
}
}
private void LoadGatheringPointFromStream(Dictionary<GatheringPointId, GatheringRoot> gatheringPoints, string fileName, Stream stream)
{
GatheringPointId gatheringPointId = ExtractGatheringPointIdFromName(fileName);
if (!(gatheringPointId == null))
{
JsonNode jsonNode = JsonNode.Parse(stream);
if (jsonNode != null)
{
_gatheringSchemaValidator.Enqueue(gatheringPointId.Value, jsonNode);
gatheringPoints[gatheringPointId] = jsonNode.Deserialize<GatheringRoot>();
}
}
}
private void LoadFromDirectory(Dictionary<GatheringPointId, GatheringRoot> gatheringPoints, DirectoryInfo directory)
{
if (!directory.Exists)
{
_logger.LogDebug("Not loading gathering points from {DirectoryName} (doesn't exist)", directory);
return;
}
FileInfo[] files = directory.GetFiles("*.json");
foreach (FileInfo fileInfo in files)
{
try
{
using FileStream stream = fileInfo.OpenRead();
LoadGatheringPointFromStream(gatheringPoints, fileInfo.Name, stream);
}
catch (JsonException ex)
{
GatheringPointId gatheringPointId = ExtractGatheringPointIdFromName(fileInfo.Name);
_logger.LogError(ex, "Unable to parse gathering point file {FileName}", fileInfo.FullName);
_questValidator.AddValidationIssue(new ValidationIssue
{
PathType = EPathType.Gathering,
Id = gatheringPointId?.Value.ToString(CultureInfo.InvariantCulture),
Location = null,
Type = EIssueType.InvalidJsonSyntax,
Severity = EIssueSeverity.Error,
Description = "JSON syntax error in " + fileInfo.Name + ": " + ex.Message
});
}
catch (Exception innerException)
{
throw new InvalidDataException("Unable to load file " + fileInfo.FullName, innerException);
}
}
DirectoryInfo[] directories = directory.GetDirectories();
foreach (DirectoryInfo directory2 in directories)
{
LoadFromDirectory(gatheringPoints, directory2);
}
}
private static GatheringPointId? ExtractGatheringPointIdFromName(string resourceName)
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(resourceName);
fileNameWithoutExtension = fileNameWithoutExtension.Substring(fileNameWithoutExtension.LastIndexOf('.') + 1);
if (!fileNameWithoutExtension.Contains('_', StringComparison.Ordinal))
{
return null;
}
return GatheringPointId.FromString(fileNameWithoutExtension.Split('_', 2)[0]);
}
public bool TryGetGatheringPoint(GatheringPointId gatheringPointId, [NotNullWhen(true)] out GatheringRoot? gatheringRoot)
{
return _gatheringPoints.TryGetValue(gatheringPointId, out gatheringRoot);
}
public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId, [NotNullWhen(true)] out GatheringPointId? gatheringPointId)
{
Dictionary<GatheringPointId, GatheringRoot> gatheringPoints = _gatheringPoints;
switch (classJobId)
{
case EClassJob.Miner:
if (_gatheringData.TryGetMinerGatheringPointByItemId(itemId, out gatheringPointId))
{
return true;
}
gatheringPointId = (from x in gatheringPoints
where x.Value.ExtraQuestItems.Contains(itemId)
select x.Key).FirstOrDefault((GatheringPointId x) => _gatheringData.MinerGatheringPoints.Contains(x));
return gatheringPointId != null;
case EClassJob.Botanist:
if (_gatheringData.TryGetBotanistGatheringPointByItemId(itemId, out gatheringPointId))
{
return true;
}
gatheringPointId = (from x in gatheringPoints
where x.Value.ExtraQuestItems.Contains(itemId)
select x.Key).FirstOrDefault((GatheringPointId x) => _gatheringData.BotanistGatheringPoints.Contains(x));
return gatheringPointId != null;
default:
gatheringPointId = null;
return false;
}
}
}