#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 _logger; private volatile Dictionary _gatheringPoints = new Dictionary(); public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, GatheringData gatheringData, GatheringSchemaValidator gatheringSchemaValidator, QuestValidator questValidator, ILogger logger) { _pluginInterface = pluginInterface; _gatheringData = gatheringData; _gatheringSchemaValidator = gatheringSchemaValidator; _questValidator = questValidator; _logger = logger; } public Dictionary Build() { Dictionary dictionary = new Dictionary(); _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 gatheringPoints) { _gatheringPoints = gatheringPoints; } public void Reload() { Publish(Build()); } [Conditional("RELEASE")] private void LoadGatheringPointsFromAssembly(Dictionary 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 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 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(); } } } private void LoadFromDirectory(Dictionary 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 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; } } }