muffin v6.12
This commit is contained in:
parent
cfb4dea47e
commit
c8197297b2
58 changed files with 40038 additions and 58059 deletions
|
@ -3,10 +3,12 @@ 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 System.Text.RegularExpressions;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
@ -45,6 +47,8 @@ internal sealed class QuestRegistry
|
|||
|
||||
private readonly List<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)> _lowPriorityContentFinderConditionQuests = new List<(uint, ElementId, int)>();
|
||||
|
||||
private readonly Dictionary<ElementId, string> _questFolderNames = new Dictionary<ElementId, string>();
|
||||
|
||||
public IEnumerable<Quest> AllQuests => _quests.Values;
|
||||
|
||||
public int Count => _quests.Count<KeyValuePair<ElementId, Quest>>((KeyValuePair<ElementId, Quest> x) => !x.Value.Root.Disabled);
|
||||
|
@ -75,6 +79,7 @@ internal sealed class QuestRegistry
|
|||
_quests.Clear();
|
||||
_contentFinderConditionIds.Clear();
|
||||
_lowPriorityContentFinderConditionQuests.Clear();
|
||||
_questFolderNames.Clear();
|
||||
LoadQuestsFromAssembly();
|
||||
try
|
||||
{
|
||||
|
@ -102,19 +107,69 @@ internal sealed class QuestRegistry
|
|||
private void LoadQuestsFromAssembly()
|
||||
{
|
||||
_logger.LogInformation("Loading quests from assembly");
|
||||
foreach (var (elementId2, root) in AssemblyQuestLoader.GetQuests())
|
||||
foreach (var (elementId2, questRoot2) in AssemblyQuestLoader.GetQuests())
|
||||
{
|
||||
try
|
||||
{
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(elementId2);
|
||||
bool? flag = null;
|
||||
DateTime? dateTime = null;
|
||||
bool flag2 = false;
|
||||
bool flag3 = false;
|
||||
try
|
||||
{
|
||||
flag = questRoot2.IsSeasonalQuest;
|
||||
flag2 = flag.HasValue;
|
||||
if (questRoot2.SeasonalQuestExpiry.HasValue)
|
||||
{
|
||||
dateTime = DateTime.SpecifyKind(questRoot2.SeasonalQuestExpiry.Value, DateTimeKind.Utc);
|
||||
flag3 = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "Failed to read seasonal fields from embedded QuestRoot for {QuestId}", elementId2);
|
||||
}
|
||||
if (_questData.TryGetQuestInfo(elementId2, out IQuestInfo questInfo))
|
||||
{
|
||||
goto IL_01c8;
|
||||
}
|
||||
if (elementId2 is UnlockLinkId unlockLinkId)
|
||||
{
|
||||
string text = unlockLinkId.ToString();
|
||||
if (text.Length > 1 && text.StartsWith('U'))
|
||||
{
|
||||
string text2 = text.Substring(1);
|
||||
string text3 = ((text2 == "568") ? "Patch 7.3 Fantasia" : ((!(text2 == "506")) ? ("U" + text2) : "Patch 7.2 Fantasia"));
|
||||
text = text3;
|
||||
}
|
||||
else
|
||||
{
|
||||
text = $"Unlock Link {unlockLinkId.Value}";
|
||||
}
|
||||
questInfo = new UnlockLinkQuestInfo(unlockLinkId, text, 0u, dateTime);
|
||||
_logger.LogDebug("Created UnlockLinkQuestInfo for {QuestId} from assembly", elementId2);
|
||||
_questData.AddOrReplaceQuestInfo(questInfo);
|
||||
goto IL_01c8;
|
||||
}
|
||||
_logger.LogWarning("Not loading unknown quest {QuestId} from assembly: Quest not found in quest data", elementId2);
|
||||
goto end_IL_003d;
|
||||
IL_01c8:
|
||||
if (flag2 || flag3)
|
||||
{
|
||||
bool flag4 = flag ?? questInfo.IsSeasonalQuest;
|
||||
_questData.ApplySeasonalOverride(elementId2, flag4, dateTime);
|
||||
_logger.LogDebug("Applied seasonal override for quest {QuestId} from assembly: IsSeasonal={IsSeasonal}, Expiry={Expiry}", elementId2, flag4, dateTime?.ToString("o") ?? "(null)");
|
||||
}
|
||||
IQuestInfo questInfo2 = _questData.GetQuestInfo(elementId2);
|
||||
Quest quest = new Quest
|
||||
{
|
||||
Id = elementId2,
|
||||
Root = root,
|
||||
Info = questInfo,
|
||||
Root = questRoot2,
|
||||
Info = questInfo2,
|
||||
Source = Quest.ESource.Assembly
|
||||
};
|
||||
_quests[quest.Id] = quest;
|
||||
end_IL_003d:;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -191,27 +246,151 @@ internal sealed class QuestRegistry
|
|||
_questValidator.Validate(_quests.Values.Where((Quest x) => x.Source != Quest.ESource.Assembly).ToList());
|
||||
}
|
||||
|
||||
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source)
|
||||
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source, string directoryName)
|
||||
{
|
||||
if (source == Quest.ESource.UserDirectory)
|
||||
{
|
||||
_logger.LogTrace("Loading quest from '{FileName}'", fileName);
|
||||
}
|
||||
ElementId elementId = ExtractQuestIdFromName(fileName);
|
||||
if (!(elementId == null))
|
||||
if (elementId == null)
|
||||
{
|
||||
JsonNode jsonNode = JsonNode.Parse(stream);
|
||||
_jsonSchemaValidator.Enqueue(elementId, jsonNode);
|
||||
QuestRoot root = jsonNode.Deserialize<QuestRoot>();
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(elementId);
|
||||
Quest quest = new Quest
|
||||
return;
|
||||
}
|
||||
JsonNode jsonNode;
|
||||
try
|
||||
{
|
||||
jsonNode = JsonNode.Parse(stream);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
ValidationIssue issue = new ValidationIssue
|
||||
{
|
||||
Id = elementId,
|
||||
Root = root,
|
||||
Info = questInfo,
|
||||
Source = source
|
||||
ElementId = elementId,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSyntax,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"JSON parsing error in file '{fileName}': {ex.Message}\n\nThis usually indicates a syntax error such as:\n\ufffd Missing comma between properties\n\ufffd Unclosed quotes or brackets\n\ufffd Invalid escape sequences\n\ufffd Trailing commas where not allowed\n\nPlease check the JSON syntax around the indicated position."
|
||||
};
|
||||
_quests[quest.Id] = quest;
|
||||
_questValidator.AddValidationIssue(issue);
|
||||
return;
|
||||
}
|
||||
_jsonSchemaValidator.Enqueue(elementId, jsonNode);
|
||||
bool? flag = null;
|
||||
DateTime? dateTime = null;
|
||||
bool flag2 = false;
|
||||
bool flag3 = false;
|
||||
if (jsonNode is JsonObject jsonObject)
|
||||
{
|
||||
if (jsonObject.TryGetPropertyValue("IsSeasonalQuest", out JsonNode jsonNode2) && jsonNode2 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
flag = jsonNode2.GetValue<bool>();
|
||||
flag2 = true;
|
||||
_logger.LogDebug("Quest {QuestId}: parsed IsSeasonalQuest override = {IsSeasonal}", elementId, flag);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "Quest {QuestId}: failed to parse IsSeasonalQuest from JSON", elementId);
|
||||
}
|
||||
}
|
||||
if (jsonObject.TryGetPropertyValue("SeasonalQuestExpiry", out JsonNode jsonNode3) && jsonNode3 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string value = jsonNode3.GetValue<string>();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
dateTime = ((!DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result)) ? new DateTime?(DateTime.Parse(value, null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal)) : new DateTime?(DateTime.SpecifyKind(result, DateTimeKind.Utc)));
|
||||
flag3 = true;
|
||||
_logger.LogDebug("Quest {QuestId}: parsed SeasonalQuestExpiry override = {Expiry}", elementId, dateTime);
|
||||
}
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
_logger.LogWarning(exception2, "Quest {QuestId}: failed to parse SeasonalQuestExpiry from JSON", elementId);
|
||||
}
|
||||
}
|
||||
}
|
||||
QuestRoot root = jsonNode.Deserialize<QuestRoot>();
|
||||
if (!_questData.TryGetQuestInfo(elementId, out IQuestInfo questInfo))
|
||||
{
|
||||
if (!(elementId is UnlockLinkId unlockLinkId))
|
||||
{
|
||||
_logger.LogWarning("Not loading unknown quest {QuestId} from project file {FileName}", elementId, fileName);
|
||||
return;
|
||||
}
|
||||
string name;
|
||||
try
|
||||
{
|
||||
string text = fileName.Substring(0, fileName.Length - ".json".Length);
|
||||
int num = text.IndexOf('_', StringComparison.Ordinal);
|
||||
string text2;
|
||||
if (num < 0 || num + 1 >= text.Length)
|
||||
{
|
||||
text2 = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
string text3 = text;
|
||||
int num2 = num + 1;
|
||||
text2 = text3.Substring(num2, text3.Length - num2);
|
||||
}
|
||||
name = text2;
|
||||
}
|
||||
catch
|
||||
{
|
||||
name = fileName.Substring(0, fileName.Length - ".json".Length);
|
||||
}
|
||||
name = NormalizeDerivedName(name);
|
||||
uint issuerDataId = 0u;
|
||||
string patch = null;
|
||||
if (jsonNode is JsonObject jsonObject2)
|
||||
{
|
||||
if (jsonObject2.TryGetPropertyValue("DataId", out JsonNode jsonNode4) && jsonNode4 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
issuerDataId = jsonNode4.GetValue<uint>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
issuerDataId = 0u;
|
||||
}
|
||||
}
|
||||
if (jsonObject2.TryGetPropertyValue("Patch", out JsonNode jsonNode5) && jsonNode5 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
patch = jsonNode5.GetValue<string>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
patch = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
questInfo = new UnlockLinkQuestInfo(unlockLinkId, name, issuerDataId, dateTime, patch);
|
||||
_logger.LogDebug("Created UnlockLinkQuestInfo for {QuestId} from project file '{FileName}'", elementId, fileName);
|
||||
_questData.AddOrReplaceQuestInfo(questInfo);
|
||||
}
|
||||
if ((flag2 || flag3) && _questData.TryGetQuestInfo(elementId, out IQuestInfo questInfo2))
|
||||
{
|
||||
_questData.ApplySeasonalOverride(elementId, flag ?? questInfo2.IsSeasonalQuest, dateTime);
|
||||
}
|
||||
Quest quest = new Quest
|
||||
{
|
||||
Id = elementId,
|
||||
Root = root,
|
||||
Info = questInfo,
|
||||
Source = source
|
||||
};
|
||||
_quests[quest.Id] = quest;
|
||||
if (!string.IsNullOrEmpty(directoryName))
|
||||
{
|
||||
_questFolderNames[elementId] = directoryName;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +411,7 @@ internal sealed class QuestRegistry
|
|||
try
|
||||
{
|
||||
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
|
||||
LoadQuestFromStream(fileInfo.Name, stream, source);
|
||||
LoadQuestFromStream(fileInfo.Name, stream, source, directory.Name);
|
||||
}
|
||||
catch (Exception innerException)
|
||||
{
|
||||
|
@ -287,4 +466,26 @@ internal sealed class QuestRegistry
|
|||
dutyOptions = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<ElementId> GetAllQuestIds()
|
||||
{
|
||||
return _quests.Keys;
|
||||
}
|
||||
|
||||
public bool TryGetQuestFolderName(ElementId questId, [NotNullWhen(true)] out string? folderName)
|
||||
{
|
||||
return _questFolderNames.TryGetValue(questId, out folderName);
|
||||
}
|
||||
|
||||
private static string NormalizeDerivedName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return name ?? string.Empty;
|
||||
}
|
||||
name = name.Replace("_", " ", StringComparison.OrdinalIgnoreCase);
|
||||
name = Regex.Replace(name, "\\s+", " ");
|
||||
name = Regex.Replace(name, "\\b(Patch)\\s+(\\d+)\\s+(\\d+)\\b", "$1 $2.$3", RegexOptions.IgnoreCase);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue